Skip to content

Commit 8709083

Browse files
authored
Codex fixes (#7)
* fix: prevent duplicate connections in pruneAndExpress recursion Pass nil accumulator to recursive pruneAndExpress calls instead of the growing connections slice. Previously, passing the populated slice caused already-accumulated entries to be appended again on each recursive return, producing duplicate connections in the substrate. Identified by Codex code review. * fix: close file handles in config loaders and experiment result writers - Add defer configFile.Close() in hyperneat.LoadYAMLConfigFile - Add defer configFile.Close() in eshyperneat.LoadYAMLConfigFile - Add explicit Close() with error handling for both output files in executor.go (experiment .dat and .npz result files) Identified by Codex code review. * fix: correct ESIterations loop boundary for lastHidden index The previous update collapsed lastHidden to just HiddenCount() (a local count) by computing lastHidden + (HiddenCount() - lastHidden). This broke multi-iteration runs by preventing subsequent passes from seeing the correct hidden node window. Fix: compute lastHidden = firstHidden + es.Layout.HiddenCount() to keep the boundary consistently in global index space. Identified by Codex code review. * fix: eliminate deadlock and data race in experiment runner Replace the competing dual-receiver errChan pattern with signal.NotifyContext: - Only main now reads from errChan (single receiver, no deadlock) - Removed signal handler goroutine that competed with main for errChan - Removed shared outer 'err' variable written from multiple goroutines (data race) - Print 'Press Ctrl+C to stop' synchronously before launching experiment - Distinguish user interrupt (ctx.Err() != nil) from real errors - Remove now-unused 'context' import Identified by Codex code review.
1 parent 6a3fb07 commit 8709083

4 files changed

Lines changed: 40 additions & 36 deletions

File tree

cppn/evolvable_substrate.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn *network.Network, graphBu
178178

179179
// move to the next window
180180
firstHiddenIter = lastHidden
181-
lastHidden = lastHidden + (es.Layout.HiddenCount() - lastHidden)
181+
lastHidden = firstHidden + es.Layout.HiddenCount()
182182
}
183183

184184
// Connect hidden nodes to the output
@@ -325,12 +325,12 @@ func (es *EvolvableSubstrate) pruneAndExpress(a, b, c float64, connections []*Qu
325325
for _, quadNode := range node.Nodes {
326326
childVariance := nodeVariance(quadNode)
327327

328-
if childVariance >= options.VarianceThreshold {
329-
if conn, err := es.pruneAndExpress(a, b, c, connections, quadNode, outgoing, options); err != nil {
330-
return nil, err
331-
} else {
332-
connections = append(connections, conn...)
333-
}
328+
if childVariance >= options.VarianceThreshold {
329+
if conn, err := es.pruneAndExpress(a, b, c, nil, quadNode, outgoing, options); err != nil {
330+
return nil, err
331+
} else {
332+
connections = append(connections, conn...)
333+
}
334334
} else if !options.LeoEnabled || (quadNode.Leo() > 0) {
335335
// Band Pruning phase.
336336
// If LEO is turned off, this should always happen.

eshyperneat/es_hyper_neat.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,9 @@ func LoadYAMLConfigFile(path string) (*Options, error) {
6161
if err != nil {
6262
return nil, errors.Wrap(err, "failed to open ES-HyperNEAT configuration file")
6363
}
64+
defer func() {
65+
_ = configFile.Close()
66+
}()
67+
6468
return LoadYAMLOptions(configFile)
6569
}

executor.go

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"context"
54
"flag"
65
"fmt"
76
"github.com/yaricom/goESHyperNEAT/v2/eshyperneat"
@@ -113,39 +112,24 @@ func main() {
113112

114113
// prepare to execute
115114
errChan := make(chan error)
116-
ctx, cancel := context.WithCancel(experimentContext)
115+
fmt.Println("\nPress Ctrl+C to stop")
116+
ctx, cancel := signal.NotifyContext(experimentContext, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
117+
defer cancel()
117118

118119
// run experiment in the separate GO routine
119120
go func() {
120-
if err = exp.Execute(ctx, startGenome, generationEvaluator, trialObserver); err != nil {
121-
errChan <- err
122-
} else {
123-
errChan <- nil
124-
}
121+
errChan <- exp.Execute(ctx, startGenome, generationEvaluator, trialObserver)
125122
}()
126123

127-
// register handler to wait for termination signals
128-
//
129-
go func(cancel context.CancelFunc) {
130-
fmt.Println("\nPress Ctrl+C to stop")
131-
132-
signals := make(chan os.Signal, 1)
133-
signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
134-
select {
135-
case <-signals:
136-
// signal to stop test fixture
137-
cancel()
138-
case err = <-errChan:
139-
// stop waiting
140-
}
141-
}(cancel)
142-
143124
// Wait for experiment completion
144125
//
145126
err = <-errChan
146127
if err != nil {
147-
// error during execution
148-
log.Fatalf("Experiment execution failed: %s", err)
128+
if ctx.Err() != nil {
129+
fmt.Println("\nExperiment interrupted by user")
130+
} else {
131+
log.Fatalf("Experiment execution failed: %s", err)
132+
}
149133
}
150134

151135
// Print experiment results statistics
@@ -157,16 +141,28 @@ func main() {
157141
expResPath := fmt.Sprintf("%s/%s.dat", outDir, *experimentName)
158142
if expResFile, err := os.Create(expResPath); err != nil {
159143
log.Fatal("Failed to create file for experiment results", err)
160-
} else if err = exp.Write(expResFile); err != nil {
161-
log.Fatal("Failed to save experiment results", err)
144+
} else {
145+
if err = exp.Write(expResFile); err != nil {
146+
_ = expResFile.Close()
147+
log.Fatal("Failed to save experiment results", err)
148+
}
149+
if err = expResFile.Close(); err != nil {
150+
log.Fatal("Failed to close file for experiment results", err)
151+
}
162152
}
163153

164154
// Save experiment data in Numpy NPZ format if requested
165155
//
166156
npzResPath := fmt.Sprintf("%s/%s.npz", outDir, *experimentName)
167157
if npzResFile, err := os.Create(npzResPath); err != nil {
168158
log.Fatalf("Failed to create file for experiment results: [%s], reason: %s", npzResPath, err)
169-
} else if err = exp.WriteNPZ(npzResFile); err != nil {
170-
log.Fatal("Failed to save experiment results as NPZ file", err)
159+
} else {
160+
if err = exp.WriteNPZ(npzResFile); err != nil {
161+
_ = npzResFile.Close()
162+
log.Fatal("Failed to save experiment results as NPZ file", err)
163+
}
164+
if err = npzResFile.Close(); err != nil {
165+
log.Fatalf("Failed to close file for experiment results: [%s], reason: %s", npzResPath, err)
166+
}
171167
}
172168
}

hyperneat/hyper_neat.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ func LoadYAMLConfigFile(path string) (*Options, error) {
5757
if err != nil {
5858
return nil, errors.Wrap(err, "failed to open HyperNEAT configuration file")
5959
}
60+
defer func() {
61+
_ = configFile.Close()
62+
}()
63+
6064
return LoadYAMLOptions(configFile)
6165
}
6266

0 commit comments

Comments
 (0)