diff --git a/.github/tickets/README.md b/.github/tickets/README.md index 41d89a1..1c3dfc4 100644 --- a/.github/tickets/README.md +++ b/.github/tickets/README.md @@ -40,8 +40,11 @@ carries what the agent needs: 3. **`Its responsibilities are:`** only where there are distinct ones. 4. **`should`** for each requirement, with the reason straight after it. 5. **`Where:`** the files and types to start from. Paths are relative to the - solution folder `SNP_Evolution/`: `SNP_Evolution/...` is the app and - `SNP_Evolution.Tests/...` the tests. + solution folder `SNP_Evolution/`, whose folders are the layer projects + (`Model/...`, `Specs/...`, `Search/...` and so on, as the README lists them) + and `SNP_Evolution.Tests/...` the tests; a file the ticket creates follows + the word "new". `python3 .github/tickets/where.py` checks that every + path in an open ticket's `Where:` line exists. 6. **`Done when:`** a checklist that a reviewer can tick without asking. 7. **`Read first:`** the spec sections, last line, no trailing period. diff --git a/.github/tickets/m2.5/m2.5-p6-1-test-fixtures.md b/.github/tickets/m2.5/m2.5-p6-1-test-fixtures.md index 7618b70..fbbfaa8 100644 --- a/.github/tickets/m2.5/m2.5-p6-1-test-fixtures.md +++ b/.github/tickets/m2.5/m2.5-p6-1-test-fixtures.md @@ -18,7 +18,7 @@ Its responsibilities are: Tests should keep their assertions, because this is a move; a test that changes what it checks belongs in another ticket. -Where: `SNP_Evolution.Tests/`, `SNP_Evolution.Tests/TestNetworks.cs`, `SNP_Evolution.Tests/Evolution/ModuleFixtures.cs`. +Where: `SNP_Evolution.Tests/`, `SNP_Evolution.Tests/Fixtures/TestNetworks.cs`, `SNP_Evolution.Tests/Fixtures/ModuleFixtures.cs`. Done when: - [ ] No builder or setup is defined in more than one test file diff --git a/.github/tickets/m2.5/m2.5-p6-2-project-split.md b/.github/tickets/m2.5/m2.5-p6-2-project-split.md index 97a4e7b..d728341 100644 --- a/.github/tickets/m2.5/m2.5-p6-2-project-split.md +++ b/.github/tickets/m2.5/m2.5-p6-2-project-split.md @@ -19,7 +19,7 @@ Its responsibilities are: The exact cut may move a type to a lower layer where a reference would otherwise point upwards, and each such move should be listed, because the cut is right only where the dependencies agree with it. `install-snp.sh` and `dotnet tool install` should work as before, because that is how the tool is used. -Where: `SNP_Evolution.slnx`, `SNP_Evolution/SNP_Evolution.csproj`, `SNP_Evolution.Tests/SNP_Evolution.Tests.csproj`, `Directory.Build.props`, `install-snp.sh`. +Where: `SNP_Evolution.slnx`, `Cli/Cli.csproj`, `SNP_Evolution.Tests/SNP_Evolution.Tests.csproj`, `Directory.Build.props`, `install-snp.sh`. Done when: - [ ] The solution builds as separate projects and no project references one above it diff --git a/.github/tickets/m2/m2-p3-1-streaming-tasks.md b/.github/tickets/m2/m2-p3-1-streaming-tasks.md index b9a0683..57ae71a 100644 --- a/.github/tickets/m2/m2-p3-1-streaming-tasks.md +++ b/.github/tickets/m2/m2-p3-1-streaming-tasks.md @@ -18,7 +18,7 @@ Its responsibilities are: The task should use the existing `Readout.SpikeTrain`, because it already returns every firing step of the output neuron. Inputs should be generated with a fixed seed per case, because the exhaustive engine needs the same input every time. -Where: new `SNP_Evolution/Evolution/Tasks/StreamingTask.cs`; `SNP_Evolution/Evolution/Tasks/SpikeTrains.cs` and `TaskSuite.cs`; `SNP_Evolution/Simulation/InputSpikes.cs`. +Where: new `Specs/Tasks/StreamingTask.cs`; `Specs/Tasks/SpikeTrains.cs` and `Search/Benchmarking/TaskSuite.cs`; `Simulation/InputSpikes.cs`. Done when: - [ ] Both tasks appear in `dotnet run -- tasks` diff --git a/.github/tickets/m2/m2-p3-2-timing-robustness.md b/.github/tickets/m2/m2-p3-2-timing-robustness.md index 9ebabd5..bcdc6be 100644 --- a/.github/tickets/m2/m2-p3-2-timing-robustness.md +++ b/.github/tickets/m2/m2-p3-2-timing-robustness.md @@ -14,7 +14,7 @@ We want a simulation option that delays each spike on each synapse by 0 to j ext The robustness score should be reported for every library part and be available as a MAP-Elites dimension, because robustness is a property to select for, not only to report. Jitter should be a sampled engine option and the exhaustive engine should refuse it, because the branching from every delay choice would explode the exhaustive search. -Where: `SNP_Evolution/Simulation/ISimulationEngine.cs` (`SimulationOptions`), `SNP_Evolution/Simulation/NetworkSimulation.cs`, `SNP_Evolution/Simulation/SequentialCpuEngine.cs`; the library summary from the `evolve-parts` command. +Where: `Simulation/SimulationOptions.cs`, `Simulation/NetworkSimulation.cs`, `Simulation/SequentialCpuEngine.cs`; the library summary from the `evolve-parts` command. Done when: - [ ] With j = 0 every result equals today's diff --git a/.github/tickets/m3/m3-p1-1-control-parts.md b/.github/tickets/m3/m3-p1-1-control-parts.md index 2693e52..9236fe4 100644 --- a/.github/tickets/m3/m3-p1-1-control-parts.md +++ b/.github/tickets/m3/m3-p1-1-control-parts.md @@ -20,7 +20,7 @@ Join's cases should include both orders, equal steps, and the largest gap the co Each contract should leave every neuron as it began, as every first part does, because a join left holding one spike fires early next round. The contracts should be general, not taken from the add loop, because they are goals for automatic discovery. -Where: `SNP_Evolution/Evolution/Contracts/FirstParts.cs` (catalogue and `Table()`), `SNP_Evolution/Evolution/Contracts/Specification.cs` (a specification for each), `SNP_Evolution/Evolution/Contracts/PartEvolution.cs`; tests in `SNP_Evolution.Tests/Evolution/FirstPartsTests.cs`. +Where: `Specs/Contracts/FirstParts.cs` (catalogue and `Table()`), `Specs/Contracts/Specification.cs` (a specification for each), `Search/PartSearch.cs`; tests in `SNP_Evolution.Tests/Specs/Contracts/FirstPartsTests.cs`. Done when: - [ ] The four contracts are in the catalogue with specifications that agree with their cases diff --git a/.github/tickets/m3/m3-p1-2-function-programs.md b/.github/tickets/m3/m3-p1-2-function-programs.md index 5316b6d..1fb4a80 100644 --- a/.github/tickets/m3/m3-p1-2-function-programs.md +++ b/.github/tickets/m3/m3-p1-2-function-programs.md @@ -20,7 +20,7 @@ The interpreter should follow every choice when the program has one, because a c A program should leave every register but the outputs at zero when it halts, and the search should check this, because a compiled part must end as it began. Each count contract in `FirstParts` and `ArithmeticParts.Count` should be a valid search target without special cases, because the route has to be general. -Where: `SNP_Evolution/Compilation/RegisterMachine.cs`, `SNP_Evolution/Compilation/ProgramSearch.cs`, `SNP_Evolution/Evolution/Contracts/Contract.cs`; tests in `SNP_Evolution.Tests/Compilation/CompilerTests.cs`. +Where: `Compilation/RegisterMachine.cs`, `Search/ProgramSearch.cs`, `Specs/Contracts/Contract.cs`; tests in `SNP_Evolution.Tests/Compilation/RegisterProgramTests.cs` and `SNP_Evolution.Tests/Search/ProgramSearchTests.cs`. Done when: - [ ] Hand-written function programs for add, increment, double, fan-out, register and zero test pass their contracts' cases in the interpreter diff --git a/.github/tickets/m3/m3-p1-3-compile-programs-to-parts.md b/.github/tickets/m3/m3-p1-3-compile-programs-to-parts.md index 2bc1447..73b7301 100644 --- a/.github/tickets/m3/m3-p1-3-compile-programs-to-parts.md +++ b/.github/tickets/m3/m3-p1-3-compile-programs-to-parts.md @@ -22,7 +22,7 @@ Shrinking should keep the cheapest network that still verifies, as parts from se The part file should record that it came from a compiled program and keep the program, because the paper must say which parts came from the compiler. A `--route search|compile|both` option should choose the route (both by default for count contracts: compile, then search from the shrunk part), because the routes should be comparable. -Where: `SNP_Evolution/Compilation/RegisterMachineCompiler.cs`, `SNP_Evolution/Evolution/Contracts/PartEvolution.cs`, `SNP_Evolution/Evolution/Contracts/PortBinding.cs`, `SNP_Evolution/Storage/PartLibraryFiles.cs`, `SNP_Evolution/Cli/PartsSession.cs`. +Where: `Compilation/RegisterMachineCompiler.cs`, `Search/PartSearch.cs`, `Specs/Contracts/PortBinding.cs`, `Storage/PartLibraryFiles.cs`, `Application/PartsService.cs`. Done when: - [ ] The hand-written function programs for register, add, increment, fan-out and zero test compile to parts that pass their contracts on the exhaustive engine diff --git a/.github/tickets/m3/m3-p1-4-compiled-parts-benchmark.md b/.github/tickets/m3/m3-p1-4-compiled-parts-benchmark.md index ce601ca..510b21d 100644 --- a/.github/tickets/m3/m3-p1-4-compiled-parts-benchmark.md +++ b/.github/tickets/m3/m3-p1-4-compiled-parts-benchmark.md @@ -15,7 +15,7 @@ The table should give, per contract, seeds solved, evaluations, and neurons, syn The n1 x n2 runs should repeat the M1 benchmark settings (10 seeds per algorithm, 6000 evaluations, lexicase, 5 sampled runs) with `--hand-built off`, because only then is the change attributable to the library. If n1 x n2 is not solved, the report should say so and give the best fitness and the parts the best networks hold, because an honest negative is still the result. -Where: `SNP_Evolution/Evolution/Benchmarking/Benchmark.cs`, the `evolve-parts` and `benchmark` commands; results in RESEARCH.md "Evolved against hand-designed arithmetic" and README "Composing machines from parts". +Where: `Search/Benchmarking/Benchmark.cs`, the `evolve-parts` and `benchmark` commands; results in RESEARCH.md "Evolved against hand-designed arithmetic" and README "Composing machines from parts". Done when: - [ ] The per-contract table is in RESEARCH.md, seeds 1 to 5 diff --git a/.github/tickets/m3/m3-p2-1-part-programs.md b/.github/tickets/m3/m3-p2-1-part-programs.md index ff07f1d..bb5bf0c 100644 --- a/.github/tickets/m3/m3-p2-1-part-programs.md +++ b/.github/tickets/m3/m3-p2-1-part-programs.md @@ -21,7 +21,7 @@ A count out-port should add to its register rather than overwrite it, because tw A register read by a call should be emptied by it, because a count port consumes what it reads; a program that needs a value twice must fan it out first, as the network must. The step estimate should be marked as an estimate, because exact timing comes only from the lowered network. -Where: new `SNP_Evolution/Evolution/Programs/` (or beside `SNP_Evolution/Compilation/RegisterMachine.cs`), `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Evolution/Modules/ModuleLibrary.cs`. +Where: new `Compilation/PartPrograms/` (or beside `Compilation/RegisterMachine.cs`), `Specs/Contracts/Specification.cs`, `Specs/Parts/ModuleLibrary.cs`. Done when: - [ ] A hand-written part program for n1 x n2 from register, add, zero test, decrement and fan-out gives every multiply case in the interpreter diff --git a/.github/tickets/m3/m3-p2-2-lower-programs.md b/.github/tickets/m3/m3-p2-2-lower-programs.md index b3c0bca..eec4ace 100644 --- a/.github/tickets/m3/m3-p2-2-lower-programs.md +++ b/.github/tickets/m3/m3-p2-2-lower-programs.md @@ -20,7 +20,7 @@ The lowering should only use parts from the library and plain relays, because a Where the lowering needs a part the library lacks (a join, a merge, a timer), it should say which, because that is a proposal the run can make. A disagreement between the interpreter and the network should be reported with the case and both readings, because it means either a lowering bug or a part whose contract does not say enough about timing. -Where: `SNP_Evolution/Evolution/Modules/Composition.cs`, `SNP_Evolution/Evolution/Modules/PartWiring.cs`, `SNP_Evolution/Evolution/Modules/Promotion.cs`; the n1 x n2 and Fibonacci programs from the previous ticket as tests. +Where: `Specs/Parts/Composition.cs`, `Specs/Parts/PartWiring.cs`, `Search/Modules/Promotion.cs`; the n1 x n2 and Fibonacci programs from the previous ticket as tests. Done when: - [ ] The hand-written n1 x n2 program lowers to a network that passes the multiply contract on the exhaustive engine and is promoted diff --git a/.github/tickets/m3/m3-p2-3-part-program-search.md b/.github/tickets/m3/m3-p2-3-part-program-search.md index eaf0850..2f7d17e 100644 --- a/.github/tickets/m3/m3-p2-3-part-program-search.md +++ b/.github/tickets/m3/m3-p2-3-part-program-search.md @@ -21,7 +21,7 @@ Its responsibilities are: Evaluations should be counted in the same units as composition search (one interpreter run per case counts as one evaluation of a network), plus every lowered network verified, because the two genomes must be compared on one budget. A program that solves in the interpreter but fails after lowering should be kept out of the population and logged, because the interpreter would otherwise reward it forever. -Where: new search beside the part-program interpreter; `SNP_Evolution/Evolution/Modules/CompositionSearch.cs` for the edit and evaluation conventions, `SNP_Evolution/Evolution/Proposals/PartProposals.cs`, `SNP_Evolution/Cli/CommandLine.cs` (`compose`), `SNP_Evolution/Evolution/Benchmarking/Benchmark.cs`. +Where: new search beside the part-program interpreter; `Search/EvolutionSearch.cs` (`CompositionSearch`) for the edit and evaluation conventions, `Search/Proposals/PartProposals.cs`, `Cli/Commands/ComposeCommand.cs`, `Search/Benchmarking/Benchmark.cs`. Done when: - [ ] `compose --task "Contract multiply" --genome program` solves from a library with no hand-built parts on at least half of 10 seeds, with the median evaluations reported next to composition search diff --git a/.github/tickets/m3/m3-p2-5-arithmetic-depth.md b/.github/tickets/m3/m3-p2-5-arithmetic-depth.md index 959484d..fcad932 100644 --- a/.github/tickets/m3/m3-p2-5-arithmetic-depth.md +++ b/.github/tickets/m3/m3-p2-5-arithmetic-depth.md @@ -16,7 +16,7 @@ Reuse should be reported per level: which promoted parts appear in each solution An n^k contract should be added to `ArithmeticParts` with a specification, keeping its cases small enough for the exhaustive engine, because it has to be verifiable to count. Sizes and steps should be added to the published-circuit table, because Zeng et al. give hand-built sizes for the same four operations. -Where: `SNP_Evolution/Evolution/Contracts/ArithmeticParts.cs`, `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Evolution/Modules/PartReuse.cs`; results in RESEARCH.md "Evolved against hand-designed arithmetic". +Where: `Specs/Contracts/ArithmeticParts.cs`, `Specs/Contracts/Specification.cs`, `Specs/Parts/PartReuse.cs`; results in RESEARCH.md "Evolved against hand-designed arithmetic". Done when: - [ ] Solved counts over 5 seeds for subtraction, division, comparison and n^k, by part-program and by composition search, are in RESEARCH.md diff --git a/.github/tickets/m3/m3-p3-1-counterexample-guided-search.md b/.github/tickets/m3/m3-p3-1-counterexample-guided-search.md index 26ef140..ff9818f 100644 --- a/.github/tickets/m3/m3-p3-1-counterexample-guided-search.md +++ b/.github/tickets/m3/m3-p3-1-counterexample-guided-search.md @@ -21,7 +21,7 @@ The loop should stop after a set number of counterexamples (8 by default) and re The counterexample should be the structured one the M2.5 verifier returns, because a counterexample as text cannot be scored. A counterexample should be added as a full case from the specification, so lexicase sees it as one more case, because a special penalty would be one more scoring rule to tune. -Where: `SNP_Evolution/Evolution/Contracts/BoundedCheck.cs` (`Admit`), `SNP_Evolution/Evolution/Contracts/PartEvolution.cs`, `SNP_Evolution/Evolution/Tasks/ContractTask.cs`, `SNP_Evolution/Evolution/Proposals/PartProposals.cs`, `SNP_Evolution/Evolution/Modules/Promotion.cs`, `SNP_Evolution/Storage/PartLibraryFiles.cs`. +Where: `Specs/Verification/BoundedCheck.cs` (`Admit`), `Search/PartSearch.cs`, `Specs/Tasks/ContractTask.cs`, `Search/Proposals/PartProposals.cs`, `Search/Modules/Promotion.cs`, `Storage/PartLibraryFiles.cs`. Done when: - [ ] Given the add loop as it was before its done waited for the accumulator, composition search with the counterexample loop on finds a version that passes admission past a = 14 diff --git a/.github/tickets/m3/m3-p3-2-proof-by-composition.md b/.github/tickets/m3/m3-p3-2-proof-by-composition.md index d5386a4..ee7a6e3 100644 --- a/.github/tickets/m3/m3-p3-2-proof-by-composition.md +++ b/.github/tickets/m3/m3-p3-2-proof-by-composition.md @@ -21,7 +21,7 @@ The use check should be stated as what the contract's cases assume and nothing m The timing of a child should come from its specification's latency bound, and a composition that relies on exact timing between children should be refused with the reason, because start and done are what make composition timing-safe, and anything else is unproven. Where the flattened check and the proof by composition both run, they should agree, and a test should cover this, because the new proof is only trusted where it has been checked. -Where: `SNP_Evolution/Evolution/Contracts/BoundedCheck.cs`, `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Evolution/Modules/Promotion.cs` (recipes), the `verify` command; tests in `SNP_Evolution.Tests/Evolution/BoundedCheckTests.cs`. +Where: `Specs/Verification/BoundedCheck.cs`, `Specs/Contracts/Specification.cs`, `Search/Modules/Promotion.cs` (recipes), the `verify` command; tests in `SNP_Evolution.Tests/Specs/Verification/BoundedCheckTests.cs`. Done when: - [ ] The add loop and the promoted multiplier are proven by composition to at least five times their flattened bounds in the same time diff --git a/.github/tickets/m3/m3-p4-1-bit-serial-adder.md b/.github/tickets/m3/m3-p4-1-bit-serial-adder.md index e56d56c..132cbc5 100644 --- a/.github/tickets/m3/m3-p4-1-bit-serial-adder.md +++ b/.github/tickets/m3/m3-p4-1-bit-serial-adder.md @@ -16,7 +16,7 @@ The comparison should give neurons, synapses and steps against Aimone et al.'s 4 The contract should be a family over width, because the binary primitives of the verified library in M5 are built on it. If the profile run also solves it, the part should be exported to NIR and checked by co-simulation, because a bit-serial adder is the part that fits integrate-and-fire hardware. -Where: `SNP_Evolution/Evolution/Contracts/ArithmeticParts.cs`, `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Evolution/Contracts/PortEncoding.cs`, `SNP_Evolution/Evolution/Contracts/PartEvolution.cs`; the published-circuit table in RESEARCH.md. +Where: `Specs/Contracts/ArithmeticParts.cs`, `Specs/Contracts/Specification.cs`, `Specs/Contracts/PortEncoding.cs`, `Search/PartSearch.cs`; the published-circuit table in RESEARCH.md. Done when: - [ ] The binary add contracts and their specifications are in the catalogue diff --git a/.github/tickets/m3/m3-p4-2-part-families.md b/.github/tickets/m3/m3-p4-2-part-families.md index a03685c..74389ae 100644 --- a/.github/tickets/m3/m3-p4-2-part-families.md +++ b/.github/tickets/m3/m3-p4-2-part-families.md @@ -19,7 +19,7 @@ A built member should be verified and proven like any other part, because compos A built member should be replaced by an evolved one only when the evolved one is cheaper, because the library already keeps the cheaper of two parts that read the same. The family's members should be one entry per k in the library, under the family's name, because composition and part programs need a concrete part to wire. -Where: `SNP_Evolution/Evolution/Contracts/Contract.cs`, `SNP_Evolution/Evolution/Contracts/FirstParts.cs`, `SNP_Evolution/Evolution/Modules/Promotion.cs`, `SNP_Evolution/Evolution/Modules/ModuleLibrary.cs`, `SNP_Evolution/Storage/PartLibraryFiles.cs`. +Where: `Specs/Contracts/Contract.cs`, `Specs/Contracts/FirstParts.cs`, `Search/Modules/Promotion.cs`, `Specs/Parts/ModuleLibrary.cs`, `Storage/PartLibraryFiles.cs`. Done when: - [ ] Delay k and sequencer k are families, and members for k up to 8 are built from the library's members, verified and proven diff --git a/.github/tickets/m3/m3-p4-3-library-compression.md b/.github/tickets/m3/m3-p4-3-library-compression.md index ad6c66c..533bde8 100644 --- a/.github/tickets/m3/m3-p4-3-library-compression.md +++ b/.github/tickets/m3/m3-p4-3-library-compression.md @@ -16,7 +16,7 @@ It should count, for each candidate sub-composition, how many promoted parts hol A candidate should only count if it has a contract that can be read off its ports and checked, because a part without a contract cannot be verified or reused. It should end in a recommendation: build compression into promotion, or leave it, with the numbers behind it. -Where: `SNP_Evolution/Evolution/Modules/Promotion.cs` (recipes), `SNP_Evolution/Evolution/Modules/PartReuse.cs`, the libraries left by the M3 benchmarks; the finding in RESEARCH.md "Toward general synthesis". +Where: `Search/Modules/Promotion.cs` (recipes), `Specs/Parts/PartReuse.cs`, the libraries left by the M3 benchmarks; the finding in RESEARCH.md "Toward general synthesis". Done when: - [ ] RESEARCH.md lists the candidate sub-compositions found, how many promoted parts hold each, and the recipe size saved diff --git a/.github/tickets/m4/m4-p1-1-algorithm-spec.md b/.github/tickets/m4/m4-p1-1-algorithm-spec.md index ebdc4dd..c167129 100644 --- a/.github/tickets/m4/m4-p1-1-algorithm-spec.md +++ b/.github/tickets/m4/m4-p1-1-algorithm-spec.md @@ -23,7 +23,7 @@ Case generation should be seeded and deterministic, because two runs of one spec A spec built for a catalogue contract should agree with that contract's cases and its `Specification`, checked with `Specifications.Disagreements`, because the catalogue is what existing parts were verified against. Training cases should include the edges of the domain (zero, one, the largest training value), because off-by-one parts pass the middle and fail the edges. -Where: new `SNP_Evolution/Evolution/Specs/AlgorithmSpec.cs`; `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Evolution/Contracts/Contract.cs`, `SNP_Evolution/Evolution/Contracts/PortEncoding.cs`, `SNP_Evolution/Evolution/Contracts/ArithmeticParts.cs`, `SNP_Evolution/Evolution/Tasks/ContractTask.cs`; tests in `SNP_Evolution.Tests/Evolution/`. +Where: new `Specs/Contracts/AlgorithmSpec.cs`; `Specs/Contracts/Specification.cs`, `Specs/Contracts/Contract.cs`, `Specs/Contracts/PortEncoding.cs`, `Specs/Contracts/ArithmeticParts.cs`, `Specs/Tasks/ContractTask.cs`; tests in `SNP_Evolution.Tests/Specs/`. Done when: - [ ] Specs for add, multiply, compare and fan-out make contracts with no disagreements against the catalogue's diff --git a/.github/tickets/m4/m4-p1-2-recurrence-task.md b/.github/tickets/m4/m4-p1-2-recurrence-task.md index e870983..7d6c737 100644 --- a/.github/tickets/m4/m4-p1-2-recurrence-task.md +++ b/.github/tickets/m4/m4-p1-2-recurrence-task.md @@ -21,7 +21,7 @@ A gap after a mistake should still earn credit when it follows the rule from the Coefficients as inputs should be optional, because a fixed rule is the first test and the family g(k) = c1 g(k-1) + c2 g(k-2) is the stretch. The compiled recurrence network should be the reference that scores 1, because `RecurrenceCompiler` already makes a network that follows the rule for any start it is built with. -Where: new `SNP_Evolution/Evolution/Tasks/RecurrenceTask.cs`; `SNP_Evolution/Evolution/Tasks/SequenceTask.cs`, `SNP_Evolution/Evolution/Tasks/TriggeredSequenceTask.cs`, `SNP_Evolution/Compilation/Recurrence.cs`, `SNP_Evolution/Compilation/RecurrenceCompiler.cs`, `SNP_Evolution/Evolution/Tasks/TaskSuite.cs`; the spec type from this epic. +Where: new `Specs/Tasks/RecurrenceTask.cs`; `Specs/Tasks/SequenceTask.cs`, `Specs/Tasks/TriggeredSequenceTask.cs`, `Compilation/Recurrence.cs`, `Compilation/RecurrenceCompiler.cs`, `Search/Benchmarking/TaskSuite.cs`; the spec type from this epic. Done when: - [ ] A network that lists the 16 Fibonacci gaps from 1,1 scores below 0.5 on the held-out cases diff --git a/.github/tickets/m4/m4-p1-3-relation-specs.md b/.github/tickets/m4/m4-p1-3-relation-specs.md index 42e4494..5c59f92 100644 --- a/.github/tickets/m4/m4-p1-3-relation-specs.md +++ b/.github/tickets/m4/m4-p1-3-relation-specs.md @@ -20,7 +20,7 @@ Every computation should have to satisfy the checker, not just one, because a pa A spec with no solution for an input should say so in its domain, and the part should answer on its no-answer done port, because a part that never fires done cannot be composed. The checker should not be used to make expected values for the case text, because two parts that give different valid answers read differently but are both right. -Where: the spec type from this epic; `SNP_Evolution/Evolution/Tasks/ContractTask.cs`, `SNP_Evolution/Evolution/Contracts/BoundedCheck.cs`, `SNP_Evolution/Evolution/Contracts/Specification.cs`, `SNP_Evolution/Simulation/ExhaustiveCpuEngine.cs`. +Where: the spec type from this epic; `Specs/Tasks/ContractTask.cs`, `Specs/Verification/BoundedCheck.cs`, `Specs/Contracts/Specification.cs`, `Simulation/ExhaustiveCpuEngine.cs`. Done when: - [ ] A "some divisor of n other than 1 and n, or no answer" spec scores a hand-built nondeterministic network as solved when every computation gives a valid divisor diff --git a/.github/tickets/m4/m4-p1-4-stream-specs.md b/.github/tickets/m4/m4-p1-4-stream-specs.md index f95d7b9..0103e84 100644 --- a/.github/tickets/m4/m4-p1-4-stream-specs.md +++ b/.github/tickets/m4/m4-p1-4-stream-specs.md @@ -18,7 +18,7 @@ Its responsibilities are: The rewritten debouncer and rate detector should give exactly the windows they give now for the same seed, because saved runs and benchmarks were scored on them. Held-out trains should run longer than training trains, because a controller that drifts or fills up shows it only after many windows. -Where: `SNP_Evolution/Evolution/Tasks/StreamingTask.cs`, `SNP_Evolution/Evolution/Tasks/SpikeTrains.cs`, `SNP_Evolution/Evolution/Tasks/TaskSuite.cs`; the spec type from this epic; tests in `SNP_Evolution.Tests/Evolution/StreamingTaskTests.cs`. +Where: `Specs/Tasks/StreamingTask.cs`, `Specs/Tasks/SpikeTrains.cs`, `Search/Benchmarking/TaskSuite.cs`; the spec type from this epic; tests in `SNP_Evolution.Tests/Specs/Tasks/StreamingTaskTests.cs`. Done when: - [ ] Debouncer and rate detector specs give the same windows as the current factory methods for seeds 1 to 4 diff --git a/.github/tickets/m4/m4-p2-1-verification-steps.md b/.github/tickets/m4/m4-p2-1-verification-steps.md index 6993197..868be62 100644 --- a/.github/tickets/m4/m4-p2-1-verification-steps.md +++ b/.github/tickets/m4/m4-p2-1-verification-steps.md @@ -21,7 +21,7 @@ A held-out failure should become a counterexample case like a proof's, because b Only candidates that pass training should go on to later steps, because held-out runs and proofs cost far more and most candidates fail training. The step reached should be part of the saved result, because a network that passed held-out cases but was never proven must not be reported as proven. -Where: new `SNP_Evolution/Evolution/Specs/SpecVerifier.cs`; `SNP_Evolution/Evolution/Contracts/BoundedCheck.cs`, `SNP_Evolution/Evolution/Contracts/PartEvolution.cs`, `SNP_Evolution/Storage/PartLibraryFiles.cs`, `SNP_Evolution/Cli/VerifyCommand.cs`. +Where: new `Specs/Verification/SpecVerifier.cs`; `Specs/Verification/BoundedCheck.cs`, `Search/PartSearch.cs`, `Storage/PartLibraryFiles.cs`, `Cli/Commands/VerifyCommand.cs`. Done when: - [ ] The add loop as it was before its done waited for the accumulator fails at the held-out or proof step with a counterexample at a = 14 or below, and the counterexample is added as a case diff --git a/.github/tickets/m4/m4-p2-2-superopt.md b/.github/tickets/m4/m4-p2-2-superopt.md index 42e40bf..a4e277b 100644 --- a/.github/tickets/m4/m4-p2-2-superopt.md +++ b/.github/tickets/m4/m4-p2-2-superopt.md @@ -23,7 +23,7 @@ A network should enter the map only after passing the verifier's held-out step, Evaluations should be charged to the shared evaluation budget in the same units as the other searches, because the synthesis arm is compared on the same budget. Under the hardware profile, seeds that do not fit should be conformed first and verified again, because conforming can change behaviour. -Where: new `SNP_Evolution/Evolution/Specs/Superoptimiser.cs`; `SNP_Evolution/Compilation/ShrinkRun.cs`, `SNP_Evolution/Evolution/MapElites.cs`, `SNP_Evolution/Evolution/HardwareCost.cs`, `SNP_Evolution/Evolution/HardwareProfile.cs`, `SNP_Evolution/Evolution/EvaluationCounter.cs`, `SNP_Evolution/Compilation/RegisterMachineCompiler.cs`, `SNP_Evolution/Compilation/RecurrenceCompiler.cs`, `SNP_Evolution/Cli/CommandLine.cs`, `SNP_Evolution/Cli/CompileSession.cs`, `SNP_Evolution/Cli/ExportCommands.cs`. +Where: new `Search/Superoptimiser.cs`; `Search/ShrinkSearch.cs`, `Search/Algorithms/MapElites.cs`, `Specs/Parts/HardwareCost.cs`, `Model/HardwareProfile.cs`, `Specs/Accounting/EvaluationBudget.cs`, `Compilation/RegisterMachineCompiler.cs`, `Compilation/RecurrenceCompiler.cs`, `Cli/Commands/CommandRegistry.cs`, `Application/CompileService.cs`, `Cli/Commands/ExportCommands.cs`. Done when: - [ ] `superopt --spec add` and `superopt --spec multiply`, seeded from compiled networks, each return a network with fewer neurons than its seed that passes held-out cases and a bounded proof, on at least 8 of 10 seeds diff --git a/.github/tickets/m4/m4-p2-3-synthesis-arm.md b/.github/tickets/m4/m4-p2-3-synthesis-arm.md index ea36744..0fa4195 100644 --- a/.github/tickets/m4/m4-p2-3-synthesis-arm.md +++ b/.github/tickets/m4/m4-p2-3-synthesis-arm.md @@ -19,7 +19,7 @@ Its responsibilities are: Both arms should get the same evaluation budget counted the same way, because otherwise the comparison measures the budget. Synthesis should not be given the compiled seed in any form, including as a library part, because that would make it superoptimisation. -Where: `SNP_Evolution/Evolution/Benchmarking/Benchmark.cs`, `SNP_Evolution/Evolution/Benchmarking/Statistics.cs`, `SNP_Evolution/Evolution/Modules/CompositionSearch.cs`, the part-program search from M3 Part 2, the superoptimiser from this epic; results in RESEARCH.md. +Where: `Search/Benchmarking/Benchmark.cs`, `Specs/Accounting/Statistics.cs`, `Search/EvolutionSearch.cs` (`CompositionSearch`), the part-program search from M3 Part 2, the superoptimiser from this epic; results in RESEARCH.md. Done when: - [ ] Seeds 1 to 10 run in both modes on the add, multiply and Fibonacci recurrence specs, with solved counts, median evaluations, best cost and the training-only rate in RESEARCH.md diff --git a/.github/tickets/m4/m4-p3-1-spec-files.md b/.github/tickets/m4/m4-p3-1-spec-files.md index f9426d6..f35d2e8 100644 --- a/.github/tickets/m4/m4-p3-1-spec-files.md +++ b/.github/tickets/m4/m4-p3-1-spec-files.md @@ -21,7 +21,7 @@ Expressions should be limited to integer arithmetic, comparisons and conditional The file should be read into the C# spec type and nothing downstream should know it came from a file, because two paths to one spec would score differently. Built-in specs should be writable as files that give the same cases, because that is the test that the format covers what the code does. -Where: new `SNP_Evolution/Evolution/Specs/SpecFile.cs`; `SNP_Evolution/Cli/CommandLine.cs`, `SNP_Evolution/Cli/CommandOptions.cs`, `SNP_Evolution/Cli/InputParsing.cs`; the spec type from M4 Part 1; tests in `SNP_Evolution.Tests/Cli/`. +Where: new `Storage/SpecFiles.cs`; `Cli/Commands/CommandRegistry.cs`, `Cli/Commands/CommonOptions.cs`, `Cli/InputParsing.cs`; the spec type from M4 Part 1; tests in `SNP_Evolution.Tests/Cli/`. Done when: - [ ] Add, multiply, compare, the Fibonacci recurrence and the divisor relation are each written as a spec file that gives the same cases as the built-in spec diff --git a/.github/tickets/m4/m4-p3-2-result-bundle.md b/.github/tickets/m4/m4-p3-2-result-bundle.md index 5d1aa6a..3b8307a 100644 --- a/.github/tickets/m4/m4-p3-2-result-bundle.md +++ b/.github/tickets/m4/m4-p3-2-result-bundle.md @@ -19,7 +19,7 @@ Its responsibilities are: The report should state what was not checked as plainly as what was, because a user taking a part to hardware needs to know whether it was proven or only tested. Exports should only be written when their co-simulation passes, and the report should say why one is missing, because a Verilog file that disagrees with the network is worse than none. -Where: `SNP_Evolution/Cli/ExportCommands.cs`, `SNP_Evolution/Export/VerilogExporter.cs`, `SNP_Evolution/Export/VerilogTestbench.cs`, `SNP_Evolution/Export/NirExporter.cs`, `SNP_Evolution/Storage/NetworkFiles.cs`, the superoptimiser and verifier from M4 Part 2; `README.md`. +Where: `Cli/Commands/ExportCommands.cs`, `Export/VerilogExporter.cs`, `Export/VerilogTestbench.cs`, `Export/NirExporter.cs`, `Storage/NetworkFiles.cs`, the superoptimiser and verifier from M4 Part 2; `README.md`. Done when: - [ ] `superopt --spec add --profile --out ` writes a bundle whose Verilog and NIR co-simulate against the network, and whose report gives the bound proven diff --git a/.github/tickets/m4/m4-p4-1-arithmetic-records.md b/.github/tickets/m4/m4-p4-1-arithmetic-records.md index 7b57fd3..430fadc 100644 --- a/.github/tickets/m4/m4-p4-1-arithmetic-records.md +++ b/.github/tickets/m4/m4-p4-1-arithmetic-records.md @@ -21,7 +21,7 @@ Rows should be compared only within one encoding, because a unary circuit costs The report should say that our seeds come from our own compiler and not from the published designs, because the published circuits could not be opened and rebuilt. Binary rows should wait for the binary adder from M3 Part 4, because no binary contract has been solved yet. -Where: the specs from M4 Part 1, `superopt` from M4 Part 2, `SNP_Evolution/Evolution/Contracts/ArithmeticParts.cs`, `SNP_Evolution/Evolution/HardwareCost.cs`; the table in RESEARCH.md "Evolved against hand-designed arithmetic". +Where: the specs from M4 Part 1, `superopt` from M4 Part 2, `Specs/Contracts/ArithmeticParts.cs`, `Specs/Parts/HardwareCost.cs`; the table in RESEARCH.md "Evolved against hand-designed arithmetic". Done when: - [ ] Seeds 1 to 10 run per spec, with and without the profile, and the best verified network per spec is saved under `parts/` or `parts-profile/` diff --git a/.github/tickets/m4/m4-p4-2-recurrence-family.md b/.github/tickets/m4/m4-p4-2-recurrence-family.md index 8fe8fba..c20890f 100644 --- a/.github/tickets/m4/m4-p4-2-recurrence-family.md +++ b/.github/tickets/m4/m4-p4-2-recurrence-family.md @@ -19,7 +19,7 @@ Its responsibilities are: Fibonacci should be one row among the family and not the headline, because a method that only finds Fibonacci is a Fibonacci method. The family with coefficients as inputs should be reported even if neither mode solves it, with where each stops, because that is the "solve equations" case and its limit is the finding. -Where: `RecurrenceTask` and the generator spec from M4 Part 1, `superopt` and the synthesis arm from M4 Part 2, `SNP_Evolution/Compilation/RecurrenceCompiler.cs`; results in RESEARCH.md. +Where: `RecurrenceTask` and the generator spec from M4 Part 1, `superopt` and the synthesis arm from M4 Part 2, `Compilation/RecurrenceCompiler.cs`; results in RESEARCH.md. Done when: - [ ] Seeds 1 to 10 run in both modes for each recurrence, with solved counts, held-out pass rates and sizes in RESEARCH.md diff --git a/.github/tickets/m4/m4-p4-3-inductive-proofs.md b/.github/tickets/m4/m4-p4-3-inductive-proofs.md index 57d2c9c..483fc6b 100644 --- a/.github/tickets/m4/m4-p4-3-inductive-proofs.md +++ b/.github/tickets/m4/m4-p4-3-inductive-proofs.md @@ -22,7 +22,7 @@ The prototype should be checked against `BoundedCheck` where both run, because a The step encoding should be written so the SMT encoding in M5 can reuse it, because exact synthesis and induction need the same terms for one step. The external solver should be optional and its tests should skip when it is missing, as the Uppaal and iverilog tests do, because not every machine has it. -Where: `SNP_Evolution/Evolution/Contracts/BoundedCheck.cs`, `SNP_Evolution/Simulation/` (the lasso tables for rule conditions), `SNP_Evolution/Export/ExternalTool.cs`; Pérez-Jiménez et al. 2024 in RESEARCH.md "Proving contracts" for the by-hand method; results in RESEARCH.md. +Where: `Specs/Verification/BoundedCheck.cs`, `Simulation/` (the lasso tables for rule conditions), `Export/ExternalTool.cs`; Pérez-Jiménez et al. 2024 in RESEARCH.md "Proving contracts" for the by-hand method; results in RESEARCH.md. Done when: - [ ] RESEARCH.md says whether one round of the add part and of the Fibonacci round can be encoded and proven for every input, with solver times, or why not diff --git a/.github/tickets/m5/m5-p1-1-synapse-weights.md b/.github/tickets/m5/m5-p1-1-synapse-weights.md index 24e3325..68bb762 100644 --- a/.github/tickets/m5/m5-p1-1-synapse-weights.md +++ b/.github/tickets/m5/m5-p1-1-synapse-weights.md @@ -21,7 +21,7 @@ Weights should be non-negative unless a target allows inhibition, because standa Weights should be off by default in every search, because results so far were found without them and must stay reproducible. The step semantics should change in one place only, because each engine and exporter repeating the delivery rule is how they drift apart. -Where: `SNP_Evolution/Networks/` (synapse and network types), `SNP_Evolution/Simulation/NetworkSimulation.cs`, `SNP_Evolution/Simulation/ExhaustiveCpuEngine.cs`, `SNP_Evolution/Simulation/Metal/`, `SNP_Evolution/Export/VerilogExporter.cs`, `SNP_Evolution/Export/NirExporter.cs`, `SNP_Evolution/Export/UppaalDeclarations.cs`, `SNP_Evolution/Evolution/Operators/StructuralMutations.cs`, `SNP_Evolution/Storage/`. +Where: `Model/` (synapse and network types), `Simulation/NetworkSimulation.cs`, `Simulation/ExhaustiveCpuEngine.cs`, `Simulation/Metal/`, `Export/VerilogExporter.cs`, `Export/NirExporter.cs`, `Export/UppaalDeclarations.cs`, `Search/Operators/StructuralMutations.cs`, `Storage/`. Done when: - [ ] A network with weights gives identical traces on every engine and under Verilog and NIR co-simulation, over 25 random weighted networks diff --git a/.github/tickets/m5/m5-p1-2-targets.md b/.github/tickets/m5/m5-p1-2-targets.md index fe8100e..5d9e42d 100644 --- a/.github/tickets/m5/m5-p1-2-targets.md +++ b/.github/tickets/m5/m5-p1-2-targets.md @@ -21,7 +21,7 @@ Its responsibilities are: Each chip limit should be cited from the vendor's documentation or a paper, and a limit with no source should be left unset rather than guessed, because a wrong limit is worse than none. `--profile hardware` should mean `--target generic-if`, because there should be one notion of fitting hardware. -Where: `SNP_Evolution/Evolution/HardwareProfile.cs`, `SNP_Evolution/Evolution/HardwareCost.cs`, `SNP_Evolution/Cli/CommandLine.cs`, `SNP_Evolution/Cli/ExportCommands.cs`, `SNP_Evolution/Cli/VerifyCommand.cs`. +Where: `Model/HardwareProfile.cs`, `Specs/Parts/HardwareCost.cs`, `Cli/Commands/CommandRegistry.cs`, `Cli/Commands/ExportCommands.cs`, `Cli/Commands/VerifyCommand.cs`. Done when: - [ ] Each target lists its limits and sources with `targets` diff --git a/.github/tickets/m5/m5-p1-3-nir-conformance.md b/.github/tickets/m5/m5-p1-3-nir-conformance.md index c1aee6f..e69afb4 100644 --- a/.github/tickets/m5/m5-p1-3-nir-conformance.md +++ b/.github/tickets/m5/m5-p1-3-nir-conformance.md @@ -19,7 +19,7 @@ Its responsibilities are: The profile should be stated so another NIR backend can implement it without our code, because the point is that parts move between simulators and chips. Expected traces should come from our engine and be checked by co-simulation before they are written, because a conformance suite with a wrong answer fails correct backends. -Where: `SNP_Evolution/Export/NirExporter.cs`, `tools/snp_nir.py`, `SNP_Evolution/Cli/ExportCommands.cs`; the document in `docs/` or RESEARCH.md. +Where: `Export/NirExporter.cs`, `tools/snp_nir.py`, `Cli/Commands/ExportCommands.cs`; the document in a new `docs/` folder or RESEARCH.md. Done when: - [ ] The profile document exists and every rule in it is covered by at least one conformance case diff --git a/.github/tickets/m5/m5-p2-1-smt-encoding.md b/.github/tickets/m5/m5-p2-1-smt-encoding.md index fba856e..08c6a61 100644 --- a/.github/tickets/m5/m5-p2-1-smt-encoding.md +++ b/.github/tickets/m5/m5-p2-1-smt-encoding.md @@ -21,7 +21,7 @@ The step should be generated from the same semantics the engines use, not writte Encoded runs of fixed networks should be checked against the engine on every step, because the encoding is only trusted once it agrees. Rules outside the encodable fragment should be refused by name, because a silently wrong encoding would make every proof built on it wrong. -Where: new `SNP_Evolution/Export/SmtExporter.cs` or the search folder from M2.5; `SNP_Evolution/Networks/SpikeCondition.cs` (lasso tables), `SNP_Evolution/Export/ExternalTool.cs`, the step semantics from M2.5. If the inductive-proof investigation in M4 built an encoding, start from it. +Where: new `Export/SmtExporter.cs` or `Search/`; `Model/SpikeCondition.cs` (lasso tables), `Export/ExternalTool.cs`, the step semantics from M2.5. If the inductive-proof investigation in M4 built an encoding, start from it. Done when: - [ ] For the first parts and 25 random profile networks, Z3 finds the encoded run equal to the engine's trace on every step diff --git a/.github/tickets/m5/m5-p2-3-windowed-improvement.md b/.github/tickets/m5/m5-p2-3-windowed-improvement.md index c02cc6b..a231f63 100644 --- a/.github/tickets/m5/m5-p2-3-windowed-improvement.md +++ b/.github/tickets/m5/m5-p2-3-windowed-improvement.md @@ -19,7 +19,7 @@ Its responsibilities are: A replacement should be kept only when the whole network still passes the verifier, because a window can be correct on its observed traces and still break timing the rest of the network relies on. Windows should follow start and done boundaries where they exist, because those are the boundaries where timing is already contracted. -Where: the exact synthesis from this epic; `SNP_Evolution/Evolution/Modules/PartWiring.cs`, `SNP_Evolution/Evolution/Modules/Composition.cs`; the superoptimiser from M4. +Where: the exact synthesis from this epic; `Specs/Parts/PartWiring.cs`, `Specs/Parts/Composition.cs`; the superoptimiser from M4. Done when: - [ ] On the compiled add and multiply networks and the hand-built Fibonacci composition, windowed improvement returns a smaller verified network, with each replacement reported diff --git a/.github/tickets/m5/m5-p2-4-proven-rewrites.md b/.github/tickets/m5/m5-p2-4-proven-rewrites.md index 0268791..f163c21 100644 --- a/.github/tickets/m5/m5-p2-4-proven-rewrites.md +++ b/.github/tickets/m5/m5-p2-4-proven-rewrites.md @@ -20,10 +20,10 @@ Its responsibilities are: A rule should only apply where its conditions hold in the whole network, because a rewrite that ignores a side input changes behaviour. Results should still go through the verifier while the method is new, because the rules are only trusted once their results agree with it. -Where: `SNP_Evolution/Compilation/ShrinkRun.cs`, `SNP_Evolution/Simulation/ExhaustiveCpuEngine.cs`, `SNP_Evolution/Networks/`; results in RESEARCH.md. +Where: `Search/ShrinkSearch.cs`, `Simulation/ExhaustiveCpuEngine.cs`, `Model/`; results in RESEARCH.md. Done when: -- [ ] RESEARCH.md lists the rules found, their conditions, and the shrink they give on the compiled add, multiply and Fibonacci networks next to `ShrinkRun`'s +- [ ] RESEARCH.md lists the rules found, their conditions, and the shrink they give on the compiled add, multiply and Fibonacci networks next to `ShrinkSearch`'s - [ ] Every network shrunk by rules passes the verifier - [ ] It ends with a build-or-leave recommendation for e-graphs - [ ] `dotnet test` green diff --git a/.github/tickets/m5/m5-p2-5-enumerative-baseline.md b/.github/tickets/m5/m5-p2-5-enumerative-baseline.md index 37752c4..fee28fa 100644 --- a/.github/tickets/m5/m5-p2-5-enumerative-baseline.md +++ b/.github/tickets/m5/m5-p2-5-enumerative-baseline.md @@ -18,7 +18,7 @@ Its responsibilities are: The behaviour key should be the same one the library uses to find duplicate parts, because two definitions of "behaves the same" would disagree. The search should report how far it got when it runs out of budget, because the size it exhausted is a lower bound on the smallest network. -Where: the search interface from M2.5; `SNP_Evolution/Evolution/Modules/ModuleLibrary.cs` (behaviour dedupe), `SNP_Evolution/Evolution/EvaluationCounter.cs`. +Where: the search interface from M2.5; `Specs/Parts/ModuleLibrary.cs` (behaviour dedupe), `Specs/Accounting/EvaluationBudget.cs`. Done when: - [ ] The enumerative search solves delay 1 to 4 and reports the size it exhausted for join and bit-serial add within the default budget diff --git a/.github/tickets/m5/m5-p2-6-search-benchmark.md b/.github/tickets/m5/m5-p2-6-search-benchmark.md index 6515e17..b3fb04e 100644 --- a/.github/tickets/m5/m5-p2-6-search-benchmark.md +++ b/.github/tickets/m5/m5-p2-6-search-benchmark.md @@ -15,7 +15,7 @@ The table should give, per spec and method: solved seeds, evaluations and wall t Methods that are deterministic should be run once and marked so, because seeds mean nothing to them. Wall time should be reported beside evaluations, because one solver call and one simulation are not the same cost. -Where: `SNP_Evolution/Evolution/Benchmarking/Benchmark.cs`, `SNP_Evolution/Evolution/Benchmarking/Statistics.cs`; results in RESEARCH.md. +Where: `Search/Benchmarking/Benchmark.cs`, `Specs/Accounting/Statistics.cs`; results in RESEARCH.md. Done when: - [ ] The table is in RESEARCH.md for delay 1 to 4, join, bit-serial add, add and multiply diff --git a/.github/tickets/m5/m5-p3-1-timing-primitives.md b/.github/tickets/m5/m5-p3-1-timing-primitives.md index f6b9d57..ae32240 100644 --- a/.github/tickets/m5/m5-p3-1-timing-primitives.md +++ b/.github/tickets/m5/m5-p3-1-timing-primitives.md @@ -20,7 +20,7 @@ Ties should be specified, not left to the network, because a part that breaks ti Parameters (k, w, r, d) should use the part-family form, because one contract per value does not scale. The published size of each primitive, where a paper gives one, should be recorded beside the spec, because the comparison is the result. -Where: the spec type from M4; `SNP_Evolution/Evolution/Contracts/FirstParts.cs`, the part families from M3; RESEARCH.md "A verified spiking parts library, and search beyond evolution" for the published designs. +Where: the spec type from M4; `Specs/Contracts/FirstParts.cs`, the part families from M3; RESEARCH.md "A verified spiking parts library, and search beyond evolution" for the published designs. Done when: - [ ] Each primitive is a spec with ties stated, for k up to 4 and w, r, d up to 8 diff --git a/.github/tickets/m5/m5-p3-2-binary-primitives.md b/.github/tickets/m5/m5-p3-2-binary-primitives.md index 99c07da..4f166d9 100644 --- a/.github/tickets/m5/m5-p3-2-binary-primitives.md +++ b/.github/tickets/m5/m5-p3-2-binary-primitives.md @@ -19,7 +19,7 @@ Its responsibilities are: A part that only works at the widths it was found at should be reported as such, because a serial part should not depend on width beyond its carry, and one that does is a lookup table. Sizes should be compared with Aimone et al.'s streaming adder and Chen and Guo's binary circuits, because those are the published binary rows. -Where: the spec type from M4; the bit-serial adder from M3 Part 4; `SNP_Evolution/Evolution/Contracts/ArithmeticParts.cs`, `SNP_Evolution/Evolution/Contracts/PortEncoding.cs`. +Where: the spec type from M4; the bit-serial adder from M3 Part 4; `Specs/Contracts/ArithmeticParts.cs`, `Specs/Contracts/PortEncoding.cs`. Done when: - [ ] Each primitive has a verified network proven for every input at widths 1 to 8 under `generic-if` diff --git a/.github/tickets/m5/m5-p3-3-state-machines.md b/.github/tickets/m5/m5-p3-3-state-machines.md index 03ce661..f1a7c02 100644 --- a/.github/tickets/m5/m5-p3-3-state-machines.md +++ b/.github/tickets/m5/m5-p3-3-state-machines.md @@ -18,7 +18,7 @@ Its responsibilities are: Generated trains should cover every transition at least once in training and in held-out runs, because a transition never exercised is never checked. Simultaneous inputs should be stated in the table (a priority, or an error output), because otherwise the machine's behaviour on them is undefined. -Where: the stream spec from M4 Part 1 and the spec file format from M4 Part 3; `SNP_Evolution/Evolution/Tasks/StreamingTask.cs`. +Where: the stream spec from M4 Part 1 and the spec file format from M4 Part 3; `Specs/Tasks/StreamingTask.cs`. Done when: - [ ] The three examples are tables that make stream specs, and the debouncer's matches the existing debouncer task diff --git a/.github/tickets/m5/m5-p3-4-library-release.md b/.github/tickets/m5/m5-p3-4-library-release.md index e444fd5..ab5b3c8 100644 --- a/.github/tickets/m5/m5-p3-4-library-release.md +++ b/.github/tickets/m5/m5-p3-4-library-release.md @@ -20,7 +20,7 @@ Its responsibilities are: A release should be rebuilt and every part verified again before it is written, because a release is a promise about every part in it. The index should be generated, never edited by hand, because hand edits drift from the parts. -Where: the result bundle from M4 Part 3; `SNP_Evolution/Storage/PartLibraryFiles.cs`, `SNP_Evolution/Cli/ExportCommands.cs`. +Where: the result bundle from M4 Part 3; `Storage/PartLibraryFiles.cs`, `Cli/Commands/ExportCommands.cs`. Done when: - [ ] `library release` writes a folder whose index lists every part, and every entry's exports co-simulate diff --git a/.github/tickets/where.py b/.github/tickets/where.py new file mode 100644 index 0000000..80a249c --- /dev/null +++ b/.github/tickets/where.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Checks that every path named in an open ticket's `Where:` line exists. + + python3 .github/tickets/where.py open issues, asked of GitHub with gh + python3 .github/tickets/where.py 46 112 only these issue numbers + +A path is a backticked token holding a slash or ending in a file extension the repository uses, so dotted type and +member names are not mistaken for paths. It is looked up from the solution folder +`SNP_Evolution/` first, as the tickets README says, then from the repository root (for `tools/`, `README.md` and the +like). A path written after "new" is one the ticket creates, so only the folder it goes in must exist. +""" +import json +import pathlib +import re +import subprocess +import sys + +EXTENSIONS = r"\.(cs|csproj|props|slnx|md|py|sh|metal|json|yml)$" + +ROOT = pathlib.Path(__file__).resolve().parents[2] +SOLUTION = ROOT / "SNP_Evolution" +TICKETS = ROOT / ".github" / "tickets" + + +def open_issues(): + out = subprocess.run(["gh", "issue", "list", "--state", "open", "--limit", "500", "--json", "number"], + check=True, capture_output=True, text=True, cwd=ROOT).stdout + return {issue["number"] for issue in json.loads(out)} + + +def tickets(numbers): + for path in sorted(TICKETS.glob("*/*.md")): + text = path.read_text() + number = re.search(r"^number:\s*(\d+)\s*$", text, re.M) + if number and int(number.group(1)) in numbers: + yield int(number.group(1)), path, text + + +def paths(where): + for match in re.finditer(r"(\bnew\s+(?:folder\s+)?)?`([^`]+)`", where): + token = match.group(2) + if "/" in token or re.search(EXTENSIONS, token): + yield token, match.group(1) is not None + + +def exists(token, new): + # The app's old single-project folder would otherwise pass from the root. + bases = (SOLUTION,) if token.startswith("SNP_Evolution/") else (SOLUTION, ROOT) + for base in bases: + target = base / token.rstrip("/") + if (target.parent if new else target).exists(): + return True + return False + + +def main(): + numbers = {int(arg) for arg in sys.argv[1:]} or open_issues() + missing = 0 + checked = 0 + found = 0 + for number, path, text in tickets(numbers): + found += 1 + where = re.search(r"^Where:(.*?)(?:\n\s*\n|\Z)", text, re.M | re.S) + if not where: + continue + for token, new in paths(where.group(1)): + checked += 1 + if not exists(token, new): + missing += 1 + print(f"#{number} {path.relative_to(ROOT)}: {'new ' if new else ''}{token}") + print(f"{checked} paths in {found} tickets, {missing} missing") + return 1 if missing else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/SNP_Evolution.iml b/.idea/SNP_Evolution.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/SNP_Evolution.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..0551ab0 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..73155d4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..58f75c8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 8985bdf..fc6ec3f 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ Thanks to Newtonsoft for the JSON parser libraries which are provided with this From the `SNP_Evolution` folder: ``` -dotnet run --project SNP_Evolution # start the console menu +dotnet run --project Cli # start the console menu dotnet test # run the test suite dotnet test --filter "Speed!=slow" # the fast set, golden runs included, in seconds ``` -Warnings are errors, and `dotnet format --verify-no-changes` must pass; both projects share `Directory.Build.props` and `.editorconfig`. Tests that run searches, time-limited bounded checks or external tools are marked `[Slow]`. CI (`.github/workflows/ci.yml`) builds, checks formatting and runs the fast set, then the slow set, on every push and pull request, on Linux with iverilog; the Metal, Uppaal and norse tests skip there. +Warnings are errors, and `dotnet format --verify-no-changes` must pass; every project shares `Directory.Build.props` and `.editorconfig`. Tests that run searches, time-limited bounded checks or external tools are marked `[Slow]`. CI (`.github/workflows/ci.yml`) builds, checks formatting and runs the fast set, then the slow set, on every push and pull request, on Linux with iverilog; the Metal, Uppaal and norse tests skip there. The golden runs in `SNP_Evolution.Tests/Golden` run fixed-seed commands (evolve, evolve-parts, compile, compose, verify and the three exports) and every engine on the reference networks and parts, and compare their output with the files in `Golden/Expected` exactly, evaluation counts included. A change that alters one fails them. When a change is meant to alter results, rerun with `SNP_UPDATE_GOLDEN=1 dotnet test --filter Golden` and say in the pull request why the files changed. @@ -30,7 +30,7 @@ sh install-snp.sh Run the script again after changing the code to reinstall the latest build. It adds `~/.dotnet/tools` to your `PATH` in `~/.zshrc` if needed. Elsewhere, from the `SNP_Evolution` folder: ``` -dotnet pack SNP_Evolution -c Release +dotnet pack Cli -c Release dotnet tool install --global --add-source ./nupkg SNP_Evolution ``` @@ -115,7 +115,7 @@ The *Rule Form* setting (Legacy, Standard or Mixed) picks the form of the rules ## Tasks -A task says what a network should do (`Evolution/Tasks/ITask`): +A task says what a network should do (`Specs/Tasks/ITask`): - **Generator**: with no input, produce exactly a set of numbers, such as a set target from the settings. - **Sequence**: with no input, space the output spikes by the target's gaps, in order (`SequenceTask`). Each gap that is wrong but close earns partial credit. @@ -124,7 +124,7 @@ A task says what a network should do (`Evolution/Tasks/ITask`): The last two read the whole output spike train (`Readout.SpikeTrain`), not just the first two spikes. `OutputTarget` turns a typed target into the matching task. - **Function**: read numbers on the input neurons, each as two spikes n steps apart, and output f(n). Examples are `n + 1`, `2n` and `n1 + n2`. Wrong but close outputs earn partial credit. - **Acceptor**: read a number on the input neuron and halt if and only if it belongs to the set. Scored by balanced accuracy. -- **Contract**: behave as a part with a start trigger, one or more done triggers and typed data ports (interval, count, trigger or binary), as a `Contract` in `Evolution/Contracts/` describes (`ContractTask`). Each case is checked against four rules: nothing is sent before start, done fires exactly once with the right outputs, every neuron ends with the spikes it started with, and done fires within the maximum latency. It reads the named port neurons (`Readout.Ports`), which the exhaustive engine follows over every computation. `ReferenceParts` holds a hand-built delay and register that meet their contracts. +- **Contract**: behave as a part with a start trigger, one or more done triggers and typed data ports (interval, count, trigger or binary), as a `Contract` in `Specs/Contracts/` describes (`ContractTask`). Each case is checked against four rules: nothing is sent before start, done fires exactly once with the right outputs, every neuron ends with the spikes it started with, and done fires within the maximum latency. It reads the named port neurons (`Readout.Ports`), which the exhaustive engine follows over every computation. `ReferenceParts` holds a hand-built delay and register that meet their contracts. - **Streaming**: a controller with no start or done (`StreamingTask`). A long sensor train arrives on the one input neuron, drawn from a fixed seed per case so every network sees the same trains, and the output's spike train is judged window by window, one check per window. Scored by balanced accuracy over windows that want output and windows that want silence, so a silent network earns 0.5 and one that copies its input less; it counts as solved only at 1. The **debouncer** answers each burst of at least m spikes within w steps with exactly one spike, and glitches of fewer spikes with none. The **rate detector** fires at least once in every window of a fast stretch (at least m spikes per w steps) and never in a slow one; the first window after each change of rate is not judged. @@ -145,7 +145,7 @@ The last two read the whole output spike train (`Readout.SpikeTrain`), not just - **MAP-Elites**: keeps the best network for every size, which maps out how fitness trades against size. - **NEAT-style speciated**: groups similar structures into species that share fitness, so new structures get time to improve. -Structural mutation (`Evolution/Operators/StructuralMutations.cs`) is a weighted mix of small edits. It can: +Structural mutation (`Search/Operators/StructuralMutations.cs`) is a weighted mix of small edits. It can: - nudge or replace an expression; - change a delay, a consumed or produced count, or whether a rule fires; @@ -337,12 +337,12 @@ The `evolve-parts` summary reports every library part's robustness at j = 1 and Both also run without the menu: ``` -dotnet run --project SNP_Evolution -c Release -- benchmark --budget 3000 --seeds 3 --task "Compute" --algorithm "MAP" -dotnet run --project SNP_Evolution -c Release -- select --task "Accept even" -dotnet run --project SNP_Evolution -c Release -- evolve --target "1,1,2,3,5,8,13" --generations 4000 --population 100 -dotnet run --project SNP_Evolution -c Release -- evolve --kind binary --target 001001001001 -dotnet run --project SNP_Evolution -c Release -- tasks # list the suite -dotnet run --project SNP_Evolution -c Release -- algorithms # list the searches +dotnet run --project Cli -c Release -- benchmark --budget 3000 --seeds 3 --task "Compute" --algorithm "MAP" +dotnet run --project Cli -c Release -- select --task "Accept even" +dotnet run --project Cli -c Release -- evolve --target "1,1,2,3,5,8,13" --generations 4000 --population 100 +dotnet run --project Cli -c Release -- evolve --kind binary --target 001001001001 +dotnet run --project Cli -c Release -- tasks # list the suite +dotnet run --project Cli -c Release -- algorithms # list the searches ``` `--task` and `--algorithm` match any name containing the text. These runs use the exhaustive engine unless given `--engine sampled`. `--lexicase on` picks parents by lexicase selection, and `--repetitions N` sets the sampled runs per network. A task or algorithm named exactly wins over those that only contain the name. Composition search builds from `--library DIR`, plus the hand-built parts with `--hand-built on`; a contract it solves is promoted into that run's library. @@ -355,36 +355,39 @@ dotnet run --project SNP_Evolution -c Release -- algorithms # list the searche snp-evolution reach --target "1,1,2,3,5,8,13,21,34" --evaluations 20000 --seeds 10 --setups flat,composition ``` -File Structure (under `SNP_Evolution/SNP_Evolution`): +The solution folder `SNP_Evolution/` holds one project per layer. They are listed lowest first, and each may reference only the projects listed before it. A project's namespace is `SnpEvolution.` followed by its folder name, and the tests in `SNP_Evolution.Tests/Layering` fail if a project references one listed after it or a namespace depends on itself. -- `Program.cs` starts the console menu, or runs a command when given arguments. -- `Networks/`: the SN P system. +- `Model/`: the SN P system. - `Rule`, `Neuron` and `Network` describe a system. - `SpikeCondition` compiles a rule expression into the exact set of spike counts it matches. - `NetworkNotation` renders a network as a readable text table. + - `HardwareProfile` says which rule forms and limits a chip takes. - `ReferenceNetworks` holds the hand-built natural and even numbers systems. - `Simulation/`: runs networks. - `CompiledNetwork` flattens a network into arrays. - `NetworkStep` steps one computation, with the rules chosen by its caller; `NetworkSimulation` chooses them at random. - `InputSpikes` describes what the environment feeds the input neurons. - `NetworkRunner` samples runs. - - The `ISimulationEngine` implementations run whole batches of trials, and declare what they support; `Metal/` holds the GPU engine. -- `Evolution/`: specifications, parts and verification, then the search. + - The `ISimulationEngine` implementations run whole batches of trials, and declare what they support; `Metal/` holds the GPU engine, whose kernel is embedded in this project. +- `Specs/`: what a network should do, and the checks that it does. + - `Accounting/` holds the `EvaluationBudget` every evaluation is charged to, what a search spent and why it stopped. - `Contracts/` holds contracts, their specifications and the catalogue built from them (the first parts and the arithmetic contracts). - `Tasks/` holds the tasks, their scoring and when a score counts as solved (`Solved`). - `Parts/` holds the part records, hand-built parts, compositions and recipes, and the module library. - `Verification/` holds the `Verifier`, the bounded check and the robustness measure. +- `Compilation/` compiles recurrences and register machines into networks. +- `Search/`: every search, behind `ISearch` and listed in `SearchCatalog`, with the run setup (`EvolutionContext`), composition search, part evolution, program search and the shrink. - `Genome/` makes random networks (`NetworkFactory`, `GenomeSpace`); `Fitness/` scores populations (`FitnessEvaluator`). - - `Operators/` holds the swappable mutation, crossover and selection operators; `Algorithms/` the genetic algorithms. + - `Operators/` holds the swappable mutation, crossover and selection operators; `Algorithms/` the genetic algorithms and the shared generation loop. - `Modules/` holds the modular loop: inserting and freezing modules, harvesting the changes that pay off, side runs on what is missing, promotion and the hand-built add loop. - - `Search/` holds the run setup (`EvolutionContext`, the algorithm catalog), composition search, part evolution and the shrink. - `Proposals/` proposes parts when a composition run stalls, from its failing checks or the shape of its target. - `Benchmarking/` holds the benchmark harness, the task suite and the algorithm selector. -- `Compilation/` compiles recurrences and register machines into networks. -- `Export/` writes Verilog, NIR and Uppaal models of networks. - `Storage/` saves and loads networks, parts and contracts as JSON, through one set of settings (`Json`), and fitness history as CSV. +- `Export/` writes Verilog, NIR and Uppaal models of networks and parts, and runs the external tools that check them. - `Application/` holds the settings and one run service per flow (evolve, compose, compile, evolve-parts, reach, benchmark, verify, export), which the menu, the command line and the tests all call. A service takes settings and a request, reports progress through a callback and returns a typed result; it never writes to the console. `Catalog` lists the engines, fitness functions, tasks and algorithms the settings offer, `PartLibraries` loads part libraries and part files, and `EvolutionSession` builds and runs one evolution. -- `Cli/` holds the console menus, one per submenu, and the command line. `Commands/` has one class per command, listed in `CommandRegistry`; `SettingOptions` declares each setting that is both a flag and a settings menu row once, for both. +- `Cli/` is the `snp-evolution` tool. `Program.cs` starts the console menu, or runs a command when given arguments. It holds the console menus, one per submenu, and the command line: `Commands/` has one class per command, listed in `CommandRegistry`, and `SettingOptions` declares each setting that is both a flag and a settings menu row once, for both. + +`SNP_Evolution.Tests/` mirrors these folders. The builders and setup more than one test needs (test networks, parts, temporary folders, console capture, engine theory data) are in `Fixtures/`, and `Golden/` holds the golden runs. ## How spikes are stored @@ -394,11 +397,13 @@ When an evolution finishes, the best network is printed and saved as a `.txt` ta ## Extending -Each swappable part is an interface plus one line in a catalog, after which it appears in the settings menu: +Each kind of extension is an interface to implement in the project it belongs to, plus one line where it is registered, after which the menus, the command line, the benchmark and the tests pick it up: -- **Simulation engine** (`ISimulationEngine`, listed in `Application/Catalog.cs`): receives a whole batch of trials (a network, its input and what to read back) so it can spread the runs out, and declares what it can run in `Support`. Read each network through `CompiledNetwork.Of(network)`, whose flat arrays are ready to copy to a GPU, and seed any per-run generators from the `Random` passed in, as `ParallelCpuEngine` does. -- **Fitness function** (`IFitnessFunction`, `Application/Catalog.cs`): scores a generator's sorted outputs from 0 to 1. -- **Task** (`ITask`, `Evolution/Benchmarking/TaskSuite.cs` or `Application/Catalog.cs`): lists its cases (input spikes and readout), then scores and describes the results. -- **Search** (`ISearch`, registered in `Evolution/Search/SearchCatalog.cs`): takes a `SearchRequest` (the task, the `EvaluationBudget` every evaluation is charged to, the run's random source and log, and optionally seeds, a stall policy and how to make and score networks) and returns a `SearchOutcome` (why it stopped, the best candidate, what it spent). A genetic algorithm only needs to implement `IGeneticAlgorithm`, built from the `EvolutionContext`, and be registered as an `EvolutionSearch`; it then runs in the shared generation loop with stall recovery and solve confirmation. Or combine new `IParentSelection`, `ICrossover` or `IMutation` operators with an existing algorithm. A network search that starts from nothing is automatically included in the benchmark and the selector; one that improves the networks it is given, such as the shrink, says `NeedsSeeds`. +- **Simulation engine**: implement `ISimulationEngine` in `Simulation/` and add it to `Catalog.Engines` in `Application/Catalog.cs`. An engine receives a whole batch of trials (a network, its input and what to read back) so it can spread the runs out, and declares what it can run in `Support`. Read each network through `CompiledNetwork.Of(network)`, whose flat arrays are ready to copy to a GPU, and seed any per-run generators from the `Random` passed in, as `ParallelCpuEngine` does. +- **Search**: implement `ISearch` in `Search/` and add it to `SearchCatalog.All` in `Search/SearchCatalog.cs` (or call `SearchCatalog.Register` at run time). A search takes a `SearchRequest` (the task, the `EvaluationBudget` every evaluation is charged to, the run's random source and log, and optionally seeds, a stall policy and how to make and score networks) and returns a `SearchOutcome` (why it stopped, the best candidate, what it spent). A genetic algorithm only needs to implement `IGeneticAlgorithm`, built from the `EvolutionContext`, and be registered as an `EvolutionSearch`; it then runs in the shared generation loop with stall recovery and solve confirmation. Or combine new `IParentSelection`, `ICrossover` or `IMutation` operators in `Search/Operators/` with an existing algorithm. A network search that starts from nothing is automatically included in the benchmark and the selector; one that improves the networks it is given, such as the shrink, says `NeedsSeeds`. +- **Task**: implement `ITask` in `Specs/Tasks/` and add it to `TaskSuite` in `Search/Benchmarking/TaskSuite.cs` for the benchmark, or to `Catalog.Tasks` in `Application/Catalog.cs` for the settings menu. A task lists its cases (input spikes and readout), then scores and describes the results. A part's behaviour is better written as a `Specification` in `Specs/Contracts/`, which the contract task, the verifier and the exporters all read. +- **Fitness function**: implement `IFitnessFunction` in `Specs/Tasks/` and add it to `Catalog.FitnessFunctions` in `Application/Catalog.cs`. It scores a generator's sorted outputs from 0 to 1. +- **Exporter**: exporters share no interface, since each target takes different inputs. Write the exporter in `Export/` as a static class that turns a network or a part into its files, add a method to `ExportService` in `Application/` that writes them and runs the target's own check, and give it a command as below, deriving from `ExportCommand` in `Cli/Commands/ExportCommands.cs`. +- **Command**: derive from `Command` in `Cli/Commands/` and add it to `CommandRegistry.All`. The command declares its name, a one-line summary and its options; usage, option parsing and errors come from the declarations, and a test checks the command table above against the registry. An option several commands take is declared once in `CommonOptions`, or in `SettingOptions` when it is also a settings menu row. The command should call a run service in `Application/` rather than run the flow itself. -A new command is one subclass of `Command` (in `Cli/Commands/`), which declares its name, a one-line summary and its options, plus one line in `CommandRegistry`. Usage, option parsing and errors come from the declarations, and a test checks the command table above against the registry. An option several commands take is declared once in `CommonOptions`, or in `SettingOptions` when it is also a settings menu row. The command should call a run service in `Application/` rather than run the flow itself. +New code goes in the lowest project that has everything it needs, and a project may only reference projects below it, so a check never depends on a search and a search never on the command line. diff --git a/SNP_Evolution/Application/Application.csproj b/SNP_Evolution/Application/Application.csproj new file mode 100644 index 0000000..d81c0cb --- /dev/null +++ b/SNP_Evolution/Application/Application.csproj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/SNP_Evolution/SNP_Evolution/Application/BenchmarkService.cs b/SNP_Evolution/Application/BenchmarkService.cs similarity index 88% rename from SNP_Evolution/SNP_Evolution/Application/BenchmarkService.cs rename to SNP_Evolution/Application/BenchmarkService.cs index b09856e..e8ec8fd 100644 --- a/SNP_Evolution/SNP_Evolution/Application/BenchmarkService.cs +++ b/SNP_Evolution/Application/BenchmarkService.cs @@ -2,17 +2,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; namespace SnpEvolution.Application { // Benchmarks searches on suite tasks, or picks the best search for one task by successive halving. Both race the // searches that start from nothing; composition search among them builds from the settings' part library. - internal static class BenchmarkService + public static class BenchmarkService { // The benchmark settings, with the library's parts when a composition search is racing; Error when the library cannot be read. public static Loaded Plan(Settings settings, BenchmarkSettings benchmark, IReadOnlyList> searches) @@ -45,5 +45,5 @@ public static string Save(IReadOnlyList rows) } // The searches to race and the settings they race with. PartsNote says where composition search's parts come from. - internal sealed record BenchmarkPlan(IReadOnlyList> Searches, BenchmarkSettings Settings, string? PartsNote); + public sealed record BenchmarkPlan(IReadOnlyList> Searches, BenchmarkSettings Settings, string? PartsNote); } diff --git a/SNP_Evolution/SNP_Evolution/Application/Catalog.cs b/SNP_Evolution/Application/Catalog.cs similarity index 93% rename from SNP_Evolution/SNP_Evolution/Application/Catalog.cs rename to SNP_Evolution/Application/Catalog.cs index 21a3864..8db11c5 100644 --- a/SNP_Evolution/SNP_Evolution/Application/Catalog.cs +++ b/SNP_Evolution/Application/Catalog.cs @@ -1,20 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; using SnpEvolution.Simulation; using SnpEvolution.Simulation.Metal; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { - internal sealed record CatalogEntry(string Name, Func Create); + public sealed record CatalogEntry(string Name, Func Create); // The swappable parts offered in the settings menu. A new engine, fitness function or task only needs to implement // its interface and be listed here, and a new search to be registered in SearchCatalog; the first entry of each list // is the default. - internal static class Catalog + public static class Catalog { // The default: the GPU where there is one, which itself hands batches too small for it to the CPU. public static readonly CatalogEntry AutoEngine = MetalEngine.IsAvailable diff --git a/SNP_Evolution/SNP_Evolution/Application/CompileService.cs b/SNP_Evolution/Application/CompileService.cs similarity index 95% rename from SNP_Evolution/SNP_Evolution/Application/CompileService.cs rename to SNP_Evolution/Application/CompileService.cs index 08326fa..4d61584 100644 --- a/SNP_Evolution/SNP_Evolution/Application/CompileService.cs +++ b/SNP_Evolution/Application/CompileService.cs @@ -3,13 +3,13 @@ using System.IO; using System.Linq; using SnpEvolution.Compilation; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Genome; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Tasks; using SnpEvolution.Storage; namespace SnpEvolution.Application @@ -18,14 +18,14 @@ namespace SnpEvolution.Application // correct by construction, checks that it is, and evolves it smaller while it stays correct. A sequence of gaps // is fitted with a recurrence; a set is generated by a register program, evolved or read from a file. // ShrinkGenerations 0 only compiles; ProgramGenerations and ProgramLexicase are for evolving a register program. - internal sealed record CompileRequest(int ShrinkGenerations = CompileService.DefaultShrinkGenerations, string? ProgramFile = null, + public sealed record CompileRequest(int ShrinkGenerations = CompileService.DefaultShrinkGenerations, string? ProgramFile = null, int ProgramGenerations = CompileService.DefaultProgramGenerations, bool ProgramLexicase = true); // Compiled is null when the target could not be compiled, with the reason in the log; Solved says whether the // network kept, shrunk or not, solves the target. - internal sealed record CompileResult(Network? Compiled, bool Solved); + public sealed record CompileResult(Network? Compiled, bool Solved); - internal static class CompileService + public static class CompileService { public const int DefaultShrinkGenerations = 300; public const int DefaultProgramGenerations = 2000; diff --git a/SNP_Evolution/SNP_Evolution/Application/ComposeService.cs b/SNP_Evolution/Application/ComposeService.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Application/ComposeService.cs rename to SNP_Evolution/Application/ComposeService.cs index 1532692..eb2ec31 100644 --- a/SNP_Evolution/SNP_Evolution/Application/ComposeService.cs +++ b/SNP_Evolution/Application/ComposeService.cs @@ -1,11 +1,11 @@ using System; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Application { // Composition search for one suite task: a contract it solves is promoted to a part and the library saved. - internal static class ComposeService + public static class ComposeService { public const string FileStem = "ComposedNet"; diff --git a/SNP_Evolution/SNP_Evolution/Application/EngineChoice.cs b/SNP_Evolution/Application/EngineChoice.cs similarity index 82% rename from SNP_Evolution/SNP_Evolution/Application/EngineChoice.cs rename to SNP_Evolution/Application/EngineChoice.cs index 3b414ab..2a37c11 100644 --- a/SNP_Evolution/SNP_Evolution/Application/EngineChoice.cs +++ b/SNP_Evolution/Application/EngineChoice.cs @@ -5,7 +5,7 @@ namespace SnpEvolution.Application { // The engine benchmarks and evolve-parts run on: exhaustive unless sampled, which follows one random choice per run. // MaxConfigurations caps how many configurations the exhaustive engine follows at once. - internal sealed record EngineChoice(bool Sampled = false, int MaxConfigurations = ExhaustiveCpuEngine.DefaultMaxConfigurations) + public sealed record EngineChoice(bool Sampled = false, int MaxConfigurations = ExhaustiveCpuEngine.DefaultMaxConfigurations) { public Func Factory => Sampled ? () => new SequentialCpuEngine() : () => new ExhaustiveCpuEngine(MaxConfigurations); diff --git a/SNP_Evolution/SNP_Evolution/Application/EvolutionSession.cs b/SNP_Evolution/Application/EvolutionSession.cs similarity index 95% rename from SNP_Evolution/SNP_Evolution/Application/EvolutionSession.cs rename to SNP_Evolution/Application/EvolutionSession.cs index eed6eca..2676491 100644 --- a/SNP_Evolution/SNP_Evolution/Application/EvolutionSession.cs +++ b/SNP_Evolution/Application/EvolutionSession.cs @@ -3,27 +3,27 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Proposals; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Genome; +using SnpEvolution.Search.Modules; +using SnpEvolution.Search.Operators; +using SnpEvolution.Search.Proposals; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Tasks; using SnpEvolution.Storage; namespace SnpEvolution.Application { // Builds one evolution run from the settings, with the stages, modules, proposals and stagnation recovery they ask // for, and runs it to the end; the run services call it. - internal static class EvolutionSession + public static class EvolutionSession { // Whether this run evolves the target a few values at a time, and if so for what. public static bool IsIterative(Settings settings, BenchmarkTask task, [NotNullWhen(true)] out IPrefixTask? prefixTask) diff --git a/SNP_Evolution/SNP_Evolution/Application/EvolveService.cs b/SNP_Evolution/Application/EvolveService.cs similarity index 83% rename from SNP_Evolution/SNP_Evolution/Application/EvolveService.cs rename to SNP_Evolution/Application/EvolveService.cs index 5e616f7..77ca97f 100644 --- a/SNP_Evolution/SNP_Evolution/Application/EvolveService.cs +++ b/SNP_Evolution/Application/EvolveService.cs @@ -1,17 +1,17 @@ using System; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Genome; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; namespace SnpEvolution.Application { // The network an evolution run starts from. - internal enum RunStart + public enum RunStart { Scratch, NaturalNumbers, @@ -19,10 +19,10 @@ internal enum RunStart } // Folder is where the files go, a new folder under runs/ unless given. - internal sealed record EvolveRequest(Settings Settings, Random Random, string FileStem, RunStart Start = RunStart.Scratch, string? Folder = null); + public sealed record EvolveRequest(Settings Settings, Random Random, string FileStem, RunStart Start = RunStart.Scratch, string? Folder = null); // Run is null, with Error saying why, when the run could not start. - internal sealed record EvolveResult(IGeneticAlgorithm? Run, bool Solved, string? Error = null) + public sealed record EvolveResult(IGeneticAlgorithm? Run, bool Solved, string? Error = null) { // How the run went, in a few words. public string Outcome @@ -39,7 +39,7 @@ public string Outcome // Evolves a network with the settings, saves the run's files and says whether it solved its task: the menu's // evolve and redo, and the evolve and compose commands. - internal static class EvolveService + public static class EvolveService { // The reference networks are generators, so they evolve towards the target whatever task is selected. public static BenchmarkTask TaskFor(Settings settings, RunStart start) => start == RunStart.Scratch diff --git a/SNP_Evolution/SNP_Evolution/Application/ExportService.cs b/SNP_Evolution/Application/ExportService.cs similarity index 94% rename from SNP_Evolution/SNP_Evolution/Application/ExportService.cs rename to SNP_Evolution/Application/ExportService.cs index 62838a6..e489731 100644 --- a/SNP_Evolution/SNP_Evolution/Application/ExportService.cs +++ b/SNP_Evolution/Application/ExportService.cs @@ -3,21 +3,21 @@ using System.IO; using System.Linq; using System.Text; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; using SnpEvolution.Export; -using SnpEvolution.Networks; +using SnpEvolution.Model; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; using SnpEvolution.Storage; namespace SnpEvolution.Application { // A part or network to export. Cases are a part's contract cases, or one run with no input for a network. - internal sealed record ExportSource(string Name, Network Network, Part? Part, IReadOnlyList Ports, + public sealed record ExportSource(string Name, Network Network, Part? Part, IReadOnlyList Ports, IReadOnlyList<(string Label, InputSpikes Input, int Steps)> Cases); - internal enum ExportStatus + public enum ExportStatus { Written, Failed, @@ -25,11 +25,11 @@ internal enum ExportStatus } // Report is what was written and checked; Error says why the export failed or how the check differed. - internal sealed record ExportResult(ExportStatus Status, string Report, string? Error = null); + public sealed record ExportResult(ExportStatus Status, string Report, string? Error = null); // Writes a network as Verilog, NIR or Uppaal, and checks the file against our engine with the outside tool when it // is installed. - internal static class ExportService + public static class ExportService { public static Loaded Part(string file) => PartLibraries.ReadPart(file).Select(loaded => { diff --git a/SNP_Evolution/SNP_Evolution/Application/Loaded.cs b/SNP_Evolution/Application/Loaded.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Application/Loaded.cs rename to SNP_Evolution/Application/Loaded.cs index d7dd2c0..c7ad92a 100644 --- a/SNP_Evolution/SNP_Evolution/Application/Loaded.cs +++ b/SNP_Evolution/Application/Loaded.cs @@ -4,7 +4,7 @@ namespace SnpEvolution.Application { // A value read from files, or why it could not be read, so callers report the reason rather than catch it. - internal sealed record Loaded(T? Value, string? Error) where T : class + public sealed record Loaded(T? Value, string? Error) where T : class { public static Loaded Of(T value) => new Loaded(value, null); diff --git a/SNP_Evolution/SNP_Evolution/Application/NetworkRunService.cs b/SNP_Evolution/Application/NetworkRunService.cs similarity index 85% rename from SNP_Evolution/SNP_Evolution/Application/NetworkRunService.cs rename to SNP_Evolution/Application/NetworkRunService.cs index 75b8ed0..5684d04 100644 --- a/SNP_Evolution/SNP_Evolution/Application/NetworkRunService.cs +++ b/SNP_Evolution/Application/NetworkRunService.cs @@ -1,17 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { // A network's outputs over the settings' runs. - internal abstract record NetworkRun(IReadOnlyList Outputs) + public abstract record NetworkRun(IReadOnlyList Outputs) { // A network with inputs, scored on the selected task. public sealed record Scored(IReadOnlyList Outputs, BenchmarkTask Task, FitnessResult Score) : NetworkRun(Outputs); @@ -21,7 +21,7 @@ public sealed record SpikeTrain(IReadOnlyList Outputs, IReadOnlyList S } // Runs one network with the settings' engine and simulation options, for the menu's Run a network. - internal static class NetworkRunService + public static class NetworkRunService { public static NetworkRun Run(Settings settings, Network network, Random random) { diff --git a/SNP_Evolution/SNP_Evolution/Application/OutputTarget.cs b/SNP_Evolution/Application/OutputTarget.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Application/OutputTarget.cs rename to SNP_Evolution/Application/OutputTarget.cs index c2d4cb5..64c745c 100644 --- a/SNP_Evolution/SNP_Evolution/Application/OutputTarget.cs +++ b/SNP_Evolution/Application/OutputTarget.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { diff --git a/SNP_Evolution/SNP_Evolution/Application/PartLibraries.cs b/SNP_Evolution/Application/PartLibraries.cs similarity index 94% rename from SNP_Evolution/SNP_Evolution/Application/PartLibraries.cs rename to SNP_Evolution/Application/PartLibraries.cs index d681602..0356e66 100644 --- a/SNP_Evolution/SNP_Evolution/Application/PartLibraries.cs +++ b/SNP_Evolution/Application/PartLibraries.cs @@ -2,14 +2,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; +using SnpEvolution.Search.Modules; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; namespace SnpEvolution.Application { // Reading part libraries and part files for every run, and saving what a composition run added to its library. - internal static class PartLibraries + public static class PartLibraries { // The library a run with these settings builds from. public static Loaded Load(Settings settings, Action? log = null) => @@ -59,5 +59,5 @@ public static void SaveIfGrown(Settings settings, ModuleLibrary? parts, int part } // A library part and the file it is saved in. - internal sealed record PartFile(LibraryPart Part, string Path); + public sealed record PartFile(LibraryPart Part, string Path); } diff --git a/SNP_Evolution/SNP_Evolution/Application/PartsService.cs b/SNP_Evolution/Application/PartsService.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Application/PartsService.cs rename to SNP_Evolution/Application/PartsService.cs index c6da153..2443109 100644 --- a/SNP_Evolution/SNP_Evolution/Application/PartsService.cs +++ b/SNP_Evolution/Application/PartsService.cs @@ -4,21 +4,21 @@ using System.IO; using System.Linq; using System.Text; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Search; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; using SnpEvolution.Storage; namespace SnpEvolution.Application { // Seed is the run's seed, which each contract's own seed comes from. - internal sealed record PartsRequest(IReadOnlyList Contracts, int Seed = RunSeed.Repeatable, bool Redo = false, EngineChoice? Engine = null, int RobustJitter = 0); + public sealed record PartsRequest(IReadOnlyList Contracts, int Seed = RunSeed.Repeatable, bool Redo = false, EngineChoice? Engine = null, int RobustJitter = 0); // Error says why nothing was evolved, when the library folder cannot be used. - internal sealed record PartsResult(IReadOnlyList Rows, string? Error = null) + public sealed record PartsResult(IReadOnlyList Rows, string? Error = null) { public bool AllSolved => Error == null && Rows.All(row => row.Part != null); } @@ -28,7 +28,7 @@ internal sealed record PartsResult(IReadOnlyList Rows, string? // the run's seed, so the same seed writes the same library whichever contracts are run together. The summary reports every part's // robustness to jitter. RobustJitter, when above 0, makes the shrink keep the most robust part at that jitter rather than the smallest. // The part budget, library folder and hardware profile come from the settings, for the menu and the command line alike. - internal static class PartsService + public static class PartsService { // Robust holds the part's robustness at each of Robustness.Reported, and is null without a part. public sealed record Row(string Contract, string Status, long? Evaluations, LibraryPart? Part, IReadOnlyList? Robust = null); diff --git a/SNP_Evolution/SNP_Evolution/Application/ReachService.cs b/SNP_Evolution/Application/ReachService.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Application/ReachService.cs rename to SNP_Evolution/Application/ReachService.cs index 4e42545..6eacea1 100644 --- a/SNP_Evolution/SNP_Evolution/Application/ReachService.cs +++ b/SNP_Evolution/Application/ReachService.cs @@ -7,19 +7,19 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; namespace SnpEvolution.Application { // Runs each setup on seeds 1..N with the same budget and compares how far into the target they get. A run's reach is // how many of the target's checks its best network gets right, in order, before the first it misses. - internal static class ReachService + public static class ReachService { public sealed record Setup(string Name, string Description, Action Apply); diff --git a/SNP_Evolution/SNP_Evolution/Application/RunAdvisor.cs b/SNP_Evolution/Application/RunAdvisor.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Application/RunAdvisor.cs rename to SNP_Evolution/Application/RunAdvisor.cs index b231c5b..1ec7561 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunAdvisor.cs +++ b/SNP_Evolution/Application/RunAdvisor.cs @@ -1,24 +1,24 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { // One change the advisor recommends, with why, and how to make it. - internal sealed record Suggestion(string Setting, string Current, string Suggested, string Reason, Action Apply); + public sealed record Suggestion(string Setting, string Current, string Suggested, string Reason, Action Apply); // What the advisor found: changes it recommends and notes that need no change, such as what a run will cost. - internal sealed record Advice(IReadOnlyList Suggestions, IReadOnlyList Notes); + public sealed record Advice(IReadOnlyList Suggestions, IReadOnlyList Notes); // Looks over the settings for the selected task before a run and suggests changes that are known to help: a // likely typo in the target, a population too small to search with, iterative evolution for long targets, // stagnation recovery, genome limits that can express the target, and a generation budget that fits the stages. // A quick pilot can also race the algorithms on an early stage of the target and pick the best one. - internal static class RunAdvisor + public static class RunAdvisor { public const int MinimumPopulation = 30; public const int SuggestedPopulation = 50; diff --git a/SNP_Evolution/SNP_Evolution/Application/RunFolders.cs b/SNP_Evolution/Application/RunFolders.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Application/RunFolders.cs rename to SNP_Evolution/Application/RunFolders.cs index 781a850..4a08f19 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunFolders.cs +++ b/SNP_Evolution/Application/RunFolders.cs @@ -5,7 +5,7 @@ namespace SnpEvolution.Application { // Where runs write their files: runs/ beside parts/ at the root of the repository the program runs in, or in the // working directory outside one, and ignored by git so runs never land in the source tree. - internal static class RunFolders + public static class RunFolders { private const string RunsFolder = "runs"; diff --git a/SNP_Evolution/SNP_Evolution/Application/RunLayers.cs b/SNP_Evolution/Application/RunLayers.cs similarity index 89% rename from SNP_Evolution/SNP_Evolution/Application/RunLayers.cs rename to SNP_Evolution/Application/RunLayers.cs index 798327c..20eaa83 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunLayers.cs +++ b/SNP_Evolution/Application/RunLayers.cs @@ -1,13 +1,13 @@ -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Proposals; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Modules; +using SnpEvolution.Search.Proposals; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { // A run is the algorithm wrapped in stages, modules, proposals and stagnation recovery; these find the layers inside. - internal static class RunLayers + public static class RunLayers { // True when the whole target was matched, rather than only an early stage of it. public static bool IsSolved(IGeneticAlgorithm geneticAlgorithm, ITask? task = null) => diff --git a/SNP_Evolution/SNP_Evolution/Application/RunNotes.cs b/SNP_Evolution/Application/RunNotes.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Application/RunNotes.cs rename to SNP_Evolution/Application/RunNotes.cs index 197f53c..6671dbd 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunNotes.cs +++ b/SNP_Evolution/Application/RunNotes.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { // What the user should know before evolving for a task: limits of the target and what the settings change. - internal static class RunNotes + public static class RunNotes { public static IReadOnlyList For(Settings settings, BenchmarkTask task) { diff --git a/SNP_Evolution/SNP_Evolution/Application/RunOutput.cs b/SNP_Evolution/Application/RunOutput.cs similarity index 95% rename from SNP_Evolution/SNP_Evolution/Application/RunOutput.cs rename to SNP_Evolution/Application/RunOutput.cs index 1d3e925..673f737 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunOutput.cs +++ b/SNP_Evolution/Application/RunOutput.cs @@ -3,20 +3,20 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Proposals; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Modules; +using SnpEvolution.Search.Proposals; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; using static SnpEvolution.Application.RunLayers; namespace SnpEvolution.Application { // What a finished run writes to its output folder and reports to the log. - internal static class RunOutput + public static class RunOutput { // Saves the fitness history and the best network, and reports the best network and what the run spent. public static void Save(IGeneticAlgorithm geneticAlgorithm, string folder, string fileStem, Action log, BudgetReport? evaluations = null) diff --git a/SNP_Evolution/SNP_Evolution/Application/RunSeed.cs b/SNP_Evolution/Application/RunSeed.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Application/RunSeed.cs rename to SNP_Evolution/Application/RunSeed.cs index 41ce848..70c3756 100644 --- a/SNP_Evolution/SNP_Evolution/Application/RunSeed.cs +++ b/SNP_Evolution/Application/RunSeed.cs @@ -4,7 +4,7 @@ namespace SnpEvolution.Application { // A run's random source: a given seed makes the run repeatable, and without one every run differs. evolve-parts and // compose write files later runs build on, so they default to seed 1 and the same command writes the same files. - internal static class RunSeed + public static class RunSeed { public const int Repeatable = 1; diff --git a/SNP_Evolution/SNP_Evolution/Application/Settings.cs b/SNP_Evolution/Application/Settings.cs similarity index 94% rename from SNP_Evolution/SNP_Evolution/Application/Settings.cs rename to SNP_Evolution/Application/Settings.cs index 948892d..798b03b 100644 --- a/SNP_Evolution/SNP_Evolution/Application/Settings.cs +++ b/SNP_Evolution/Application/Settings.cs @@ -1,17 +1,17 @@ using System.Collections.Generic; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Proposals; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Genome; +using SnpEvolution.Search.Modules; +using SnpEvolution.Search.Proposals; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Application { - internal sealed class Settings + public sealed class Settings { public const int Elitism = SearchCatalog.Elitism; public const int MaxSpikeGroupSize = 4; @@ -37,7 +37,7 @@ internal sealed class Settings public int MaxInitialSpikes { get; set; } = MaxSpikeGroupSize; public bool DuplicateNeurons { get; set; } - // Keep every rule to threshold-and-reset forms, so evolved networks export to NIR (see Evolution.HardwareProfile). + // Keep every rule to threshold-and-reset forms, so evolved networks export to NIR (see Model.HardwareProfile). public bool HardwareProfile { get; set; } // Evolve sequences and binary words a few values at a time. Zero start length or step means automatic. diff --git a/SNP_Evolution/SNP_Evolution/Application/TargetPatterns.cs b/SNP_Evolution/Application/TargetPatterns.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Application/TargetPatterns.cs rename to SNP_Evolution/Application/TargetPatterns.cs diff --git a/SNP_Evolution/SNP_Evolution/Application/VerifyService.cs b/SNP_Evolution/Application/VerifyService.cs similarity index 83% rename from SNP_Evolution/SNP_Evolution/Application/VerifyService.cs rename to SNP_Evolution/Application/VerifyService.cs index 0abbff9..c63b4b9 100644 --- a/SNP_Evolution/SNP_Evolution/Application/VerifyService.cs +++ b/SNP_Evolution/Application/VerifyService.cs @@ -2,19 +2,19 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; using SnpEvolution.Storage; namespace SnpEvolution.Application { // One part's proof: the bound it holds to, and the counterexample when it fails. - internal sealed record VerifiedPart(string Contract, ProvenBound Proven, TimeSpan Elapsed, string? Counterexample); + public sealed record VerifiedPart(string Contract, ProvenBound Proven, TimeSpan Elapsed, string? Counterexample); // Proves each part's contract for every input up to a bound, raised until the time per part runs out, and records // the bound in the part's file. - internal static class VerifyService + public static class VerifyService { // Reports each part as it is proven, since a proof can take the whole time allowed. public static IReadOnlyList Run(IReadOnlyList parts, ProofLimits limits, Action proven) diff --git a/SNP_Evolution/SNP_Evolution/Cli/BenchmarkMenu.cs b/SNP_Evolution/Cli/BenchmarkMenu.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Cli/BenchmarkMenu.cs rename to SNP_Evolution/Cli/BenchmarkMenu.cs index df6e7bf..61e1a82 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/BenchmarkMenu.cs +++ b/SNP_Evolution/Cli/BenchmarkMenu.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Diagnostics; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/Cli/Cli.csproj b/SNP_Evolution/Cli/Cli.csproj new file mode 100644 index 0000000..062448a --- /dev/null +++ b/SNP_Evolution/Cli/Cli.csproj @@ -0,0 +1,17 @@ + + + + Exe + 1.0.0 + true + true + snp-evolution + SNP_Evolution + $(MSBuildThisFileDirectory)../nupkg + + + + + + + diff --git a/SNP_Evolution/SNP_Evolution/Cli/CommandLine.cs b/SNP_Evolution/Cli/CommandLine.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/CommandLine.cs rename to SNP_Evolution/Cli/CommandLine.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/AdviseCommand.cs b/SNP_Evolution/Cli/Commands/AdviseCommand.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/AdviseCommand.cs rename to SNP_Evolution/Cli/Commands/AdviseCommand.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/BenchmarkArgs.cs b/SNP_Evolution/Cli/Commands/BenchmarkArgs.cs similarity index 95% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/BenchmarkArgs.cs rename to SNP_Evolution/Cli/Commands/BenchmarkArgs.cs index c6b1c11..7d908d5 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/BenchmarkArgs.cs +++ b/SNP_Evolution/Cli/Commands/BenchmarkArgs.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; namespace SnpEvolution.Cli { @@ -99,7 +99,7 @@ public override ExitCode Run(CommandArgs args) Console.WriteLine("Best algorithm for {0}: {1}", tasks[0].Name, result.Winner.Name); if (result.BestFound is Individual best) { - Console.WriteLine("Best network found (fitness {0}, {1}):\n{2}", best.Fitness, best.Description, Networks.NetworkNotation.Format(best.Genes)); + Console.WriteLine("Best network found (fitness {0}, {1}):\n{2}", best.Fitness, best.Description, Model.NetworkNotation.Format(best.Genes)); } return ExitCode.Success; } diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/Command.cs b/SNP_Evolution/Cli/Commands/Command.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/Command.cs rename to SNP_Evolution/Cli/Commands/Command.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/CommandArgs.cs b/SNP_Evolution/Cli/Commands/CommandArgs.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/CommandArgs.cs rename to SNP_Evolution/Cli/Commands/CommandArgs.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/CommandRegistry.cs b/SNP_Evolution/Cli/Commands/CommandRegistry.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/CommandRegistry.cs rename to SNP_Evolution/Cli/Commands/CommandRegistry.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/CommonOptions.cs b/SNP_Evolution/Cli/Commands/CommonOptions.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/CommonOptions.cs rename to SNP_Evolution/Cli/Commands/CommonOptions.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/CompileCommand.cs b/SNP_Evolution/Cli/Commands/CompileCommand.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/CompileCommand.cs rename to SNP_Evolution/Cli/Commands/CompileCommand.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/ComposeCommand.cs b/SNP_Evolution/Cli/Commands/ComposeCommand.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/ComposeCommand.cs rename to SNP_Evolution/Cli/Commands/ComposeCommand.cs index 545d8f7..bc755a8 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/ComposeCommand.cs +++ b/SNP_Evolution/Cli/Commands/ComposeCommand.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/EvolveCommand.cs b/SNP_Evolution/Cli/Commands/EvolveCommand.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/EvolveCommand.cs rename to SNP_Evolution/Cli/Commands/EvolveCommand.cs index 59f7cec..927f6df 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/EvolveCommand.cs +++ b/SNP_Evolution/Cli/Commands/EvolveCommand.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs b/SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs rename to SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs index d094c24..f8b4139 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs +++ b/SNP_Evolution/Cli/Commands/EvolvePartsCommand.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Contracts; +using SnpEvolution.Specs.Contracts; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/ExportCommands.cs b/SNP_Evolution/Cli/Commands/ExportCommands.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/ExportCommands.cs rename to SNP_Evolution/Cli/Commands/ExportCommands.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/ListCommands.cs b/SNP_Evolution/Cli/Commands/ListCommands.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/ListCommands.cs rename to SNP_Evolution/Cli/Commands/ListCommands.cs index aa67f6b..5cb1aad 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/ListCommands.cs +++ b/SNP_Evolution/Cli/Commands/ListCommands.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/Option.cs b/SNP_Evolution/Cli/Commands/Option.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/Option.cs rename to SNP_Evolution/Cli/Commands/Option.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/ReachCommand.cs b/SNP_Evolution/Cli/Commands/ReachCommand.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/ReachCommand.cs rename to SNP_Evolution/Cli/Commands/ReachCommand.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/TargetArgs.cs b/SNP_Evolution/Cli/Commands/TargetArgs.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/TargetArgs.cs rename to SNP_Evolution/Cli/Commands/TargetArgs.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/ValueKind.cs b/SNP_Evolution/Cli/Commands/ValueKind.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/ValueKind.cs rename to SNP_Evolution/Cli/Commands/ValueKind.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/Commands/VerifyCommand.cs b/SNP_Evolution/Cli/Commands/VerifyCommand.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Cli/Commands/VerifyCommand.cs rename to SNP_Evolution/Cli/Commands/VerifyCommand.cs index 146e3a5..77b2896 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/Commands/VerifyCommand.cs +++ b/SNP_Evolution/Cli/Commands/VerifyCommand.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Specs.Verification; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/CompileMenu.cs b/SNP_Evolution/Cli/CompileMenu.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/CompileMenu.cs rename to SNP_Evolution/Cli/CompileMenu.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/ConsoleInput.cs b/SNP_Evolution/Cli/ConsoleInput.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/ConsoleInput.cs rename to SNP_Evolution/Cli/ConsoleInput.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/ConsoleUi.cs b/SNP_Evolution/Cli/ConsoleUi.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/ConsoleUi.cs rename to SNP_Evolution/Cli/ConsoleUi.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/EvolveMenu.cs b/SNP_Evolution/Cli/EvolveMenu.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Cli/EvolveMenu.cs rename to SNP_Evolution/Cli/EvolveMenu.cs index 00ce2a8..0495cdd 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/EvolveMenu.cs +++ b/SNP_Evolution/Cli/EvolveMenu.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/InputParsing.cs b/SNP_Evolution/Cli/InputParsing.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/InputParsing.cs rename to SNP_Evolution/Cli/InputParsing.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/MainMenu.cs b/SNP_Evolution/Cli/MainMenu.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/MainMenu.cs rename to SNP_Evolution/Cli/MainMenu.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/MenuPrompts.cs b/SNP_Evolution/Cli/MenuPrompts.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/MenuPrompts.cs rename to SNP_Evolution/Cli/MenuPrompts.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/PartsMenu.cs b/SNP_Evolution/Cli/PartsMenu.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Cli/PartsMenu.cs rename to SNP_Evolution/Cli/PartsMenu.cs index 83680f0..5887348 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/PartsMenu.cs +++ b/SNP_Evolution/Cli/PartsMenu.cs @@ -1,6 +1,6 @@ using System; using SnpEvolution.Application; -using SnpEvolution.Evolution.Contracts; +using SnpEvolution.Specs.Contracts; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Program.cs b/SNP_Evolution/Cli/Program.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Program.cs rename to SNP_Evolution/Cli/Program.cs index 801d59f..d616d3b 100644 --- a/SNP_Evolution/SNP_Evolution/Program.cs +++ b/SNP_Evolution/Cli/Program.cs @@ -1,7 +1,7 @@ using System; using SnpEvolution.Cli; -namespace SnpEvolution +namespace SnpEvolution.Cli { internal static class Program { diff --git a/SNP_Evolution/SNP_Evolution/Cli/RedoMenu.cs b/SNP_Evolution/Cli/RedoMenu.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/RedoMenu.cs rename to SNP_Evolution/Cli/RedoMenu.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/RunMenu.cs b/SNP_Evolution/Cli/RunMenu.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Cli/RunMenu.cs rename to SNP_Evolution/Cli/RunMenu.cs index 36d8435..c63cd8b 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/RunMenu.cs +++ b/SNP_Evolution/Cli/RunMenu.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics; using SnpEvolution.Application; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Specs.Tasks; using SnpEvolution.Storage; namespace SnpEvolution.Cli diff --git a/SNP_Evolution/SNP_Evolution/Cli/SavedRuns.cs b/SNP_Evolution/Cli/SavedRuns.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Cli/SavedRuns.cs rename to SNP_Evolution/Cli/SavedRuns.cs index cb9548a..2c75331 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/SavedRuns.cs +++ b/SNP_Evolution/Cli/SavedRuns.cs @@ -6,10 +6,10 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; +using SnpEvolution.Search; +using SnpEvolution.Search.Benchmarking; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Tasks; using SnpEvolution.Storage; namespace SnpEvolution.Cli diff --git a/SNP_Evolution/SNP_Evolution/Cli/SearchMenu.cs b/SNP_Evolution/Cli/SearchMenu.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/SearchMenu.cs rename to SNP_Evolution/Cli/SearchMenu.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/SettingOptions.cs b/SNP_Evolution/Cli/SettingOptions.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Cli/SettingOptions.cs rename to SNP_Evolution/Cli/SettingOptions.cs index acfc40b..a20b2c9 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/SettingOptions.cs +++ b/SNP_Evolution/Cli/SettingOptions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using SnpEvolution.Application; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; namespace SnpEvolution.Cli { diff --git a/SNP_Evolution/SNP_Evolution/Cli/SettingsMenu.cs b/SNP_Evolution/Cli/SettingsMenu.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Cli/SettingsMenu.cs rename to SNP_Evolution/Cli/SettingsMenu.cs index 8ee4b33..9bb86e4 100644 --- a/SNP_Evolution/SNP_Evolution/Cli/SettingsMenu.cs +++ b/SNP_Evolution/Cli/SettingsMenu.cs @@ -1,5 +1,5 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Benchmarking; +using SnpEvolution.Search.Benchmarking; using static SnpEvolution.Cli.MenuPrompts; namespace SnpEvolution.Cli diff --git a/SNP_Evolution/SNP_Evolution/Cli/SettingsPage.cs b/SNP_Evolution/Cli/SettingsPage.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/SettingsPage.cs rename to SNP_Evolution/Cli/SettingsPage.cs diff --git a/SNP_Evolution/SNP_Evolution/Cli/TargetMenu.cs b/SNP_Evolution/Cli/TargetMenu.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Cli/TargetMenu.cs rename to SNP_Evolution/Cli/TargetMenu.cs diff --git a/SNP_Evolution/Compilation/Compilation.csproj b/SNP_Evolution/Compilation/Compilation.csproj new file mode 100644 index 0000000..1350f7a --- /dev/null +++ b/SNP_Evolution/Compilation/Compilation.csproj @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SNP_Evolution/SNP_Evolution/Compilation/Recurrence.cs b/SNP_Evolution/Compilation/Recurrence.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Compilation/Recurrence.cs rename to SNP_Evolution/Compilation/Recurrence.cs diff --git a/SNP_Evolution/SNP_Evolution/Compilation/RecurrenceCompiler.cs b/SNP_Evolution/Compilation/RecurrenceCompiler.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Compilation/RecurrenceCompiler.cs rename to SNP_Evolution/Compilation/RecurrenceCompiler.cs index d333a5c..7729fd9 100644 --- a/SNP_Evolution/SNP_Evolution/Compilation/RecurrenceCompiler.cs +++ b/SNP_Evolution/Compilation/RecurrenceCompiler.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using SnpEvolution.Networks; +using SnpEvolution.Model; namespace SnpEvolution.Compilation { diff --git a/SNP_Evolution/SNP_Evolution/Compilation/RegisterMachine.cs b/SNP_Evolution/Compilation/RegisterMachine.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Compilation/RegisterMachine.cs rename to SNP_Evolution/Compilation/RegisterMachine.cs diff --git a/SNP_Evolution/SNP_Evolution/Compilation/RegisterMachineCompiler.cs b/SNP_Evolution/Compilation/RegisterMachineCompiler.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Compilation/RegisterMachineCompiler.cs rename to SNP_Evolution/Compilation/RegisterMachineCompiler.cs index b2617da..ba3fb83 100644 --- a/SNP_Evolution/SNP_Evolution/Compilation/RegisterMachineCompiler.cs +++ b/SNP_Evolution/Compilation/RegisterMachineCompiler.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Networks; +using SnpEvolution.Model; namespace SnpEvolution.Compilation { diff --git a/SNP_Evolution/Directory.Build.props b/SNP_Evolution/Directory.Build.props index 72be5c2..cf5682b 100644 --- a/SNP_Evolution/Directory.Build.props +++ b/SNP_Evolution/Directory.Build.props @@ -1,12 +1,19 @@ - + net10.0 14.0 enable enable true + false + SnpEvolution.$(MSBuildProjectName) + SnpEvolution.$(MSBuildProjectName) + + + + diff --git a/SNP_Evolution/Export/Export.csproj b/SNP_Evolution/Export/Export.csproj new file mode 100644 index 0000000..697919b --- /dev/null +++ b/SNP_Evolution/Export/Export.csproj @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SNP_Evolution/SNP_Evolution/Export/ExternalTool.cs b/SNP_Evolution/Export/ExternalTool.cs similarity index 100% rename from SNP_Evolution/SNP_Evolution/Export/ExternalTool.cs rename to SNP_Evolution/Export/ExternalTool.cs diff --git a/SNP_Evolution/SNP_Evolution/Export/NetworkPort.cs b/SNP_Evolution/Export/NetworkPort.cs similarity index 92% rename from SNP_Evolution/SNP_Evolution/Export/NetworkPort.cs rename to SNP_Evolution/Export/NetworkPort.cs index 8f418be..1b35296 100644 --- a/SNP_Evolution/SNP_Evolution/Export/NetworkPort.cs +++ b/SNP_Evolution/Export/NetworkPort.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; namespace SnpEvolution.Export { diff --git a/SNP_Evolution/SNP_Evolution/Export/NirExporter.cs b/SNP_Evolution/Export/NirExporter.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Export/NirExporter.cs rename to SNP_Evolution/Export/NirExporter.cs index 84ab822..681d9ff 100644 --- a/SNP_Evolution/SNP_Evolution/Export/NirExporter.cs +++ b/SNP_Evolution/Export/NirExporter.cs @@ -3,11 +3,11 @@ using System.IO; using System.Linq; using Newtonsoft.Json; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; +using SnpEvolution.Model; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; using SnpEvolution.Storage; namespace SnpEvolution.Export diff --git a/SNP_Evolution/SNP_Evolution/Export/UppaalDeclarations.cs b/SNP_Evolution/Export/UppaalDeclarations.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Export/UppaalDeclarations.cs rename to SNP_Evolution/Export/UppaalDeclarations.cs index a7261ac..2705d0d 100644 --- a/SNP_Evolution/SNP_Evolution/Export/UppaalDeclarations.cs +++ b/SNP_Evolution/Export/UppaalDeclarations.cs @@ -2,11 +2,11 @@ using System.Globalization; using System.Linq; using System.Text; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Tasks; +using SnpEvolution.Specs.Verification; namespace SnpEvolution.Export { diff --git a/SNP_Evolution/SNP_Evolution/Export/UppaalExporter.cs b/SNP_Evolution/Export/UppaalExporter.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Export/UppaalExporter.cs rename to SNP_Evolution/Export/UppaalExporter.cs index 4522fc4..0ca3d78 100644 --- a/SNP_Evolution/SNP_Evolution/Export/UppaalExporter.cs +++ b/SNP_Evolution/Export/UppaalExporter.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Security; using System.Text; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Tasks; +using SnpEvolution.Specs.Verification; using SnpEvolution.Storage; namespace SnpEvolution.Export diff --git a/SNP_Evolution/SNP_Evolution/Export/VerilogExporter.cs b/SNP_Evolution/Export/VerilogExporter.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Export/VerilogExporter.cs rename to SNP_Evolution/Export/VerilogExporter.cs index b89b254..bba210d 100644 --- a/SNP_Evolution/SNP_Evolution/Export/VerilogExporter.cs +++ b/SNP_Evolution/Export/VerilogExporter.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; +using SnpEvolution.Model; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; namespace SnpEvolution.Export { diff --git a/SNP_Evolution/SNP_Evolution/Export/VerilogNeuronModules.cs b/SNP_Evolution/Export/VerilogNeuronModules.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Export/VerilogNeuronModules.cs rename to SNP_Evolution/Export/VerilogNeuronModules.cs index b09c0e9..a0f4c66 100644 --- a/SNP_Evolution/SNP_Evolution/Export/VerilogNeuronModules.cs +++ b/SNP_Evolution/Export/VerilogNeuronModules.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using SnpEvolution.Networks; +using SnpEvolution.Model; using SnpEvolution.Simulation; namespace SnpEvolution.Export diff --git a/SNP_Evolution/SNP_Evolution/Export/VerilogTestbench.cs b/SNP_Evolution/Export/VerilogTestbench.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Export/VerilogTestbench.cs rename to SNP_Evolution/Export/VerilogTestbench.cs index 2e3050a..b5d879d 100644 --- a/SNP_Evolution/SNP_Evolution/Export/VerilogTestbench.cs +++ b/SNP_Evolution/Export/VerilogTestbench.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; +using SnpEvolution.Model; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Verification; namespace SnpEvolution.Export { diff --git a/SNP_Evolution/SNP_Evolution/Networks/DelayKind.cs b/SNP_Evolution/Model/DelayKind.cs similarity index 95% rename from SNP_Evolution/SNP_Evolution/Networks/DelayKind.cs rename to SNP_Evolution/Model/DelayKind.cs index e3bec4a..b80d56d 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/DelayKind.cs +++ b/SNP_Evolution/Model/DelayKind.cs @@ -1,4 +1,4 @@ -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // What a rule's delay d does to its neuron, decided once by Rule for every engine and exporter. public enum DelayKind diff --git a/SNP_Evolution/SNP_Evolution/Networks/ExpressionParser.cs b/SNP_Evolution/Model/ExpressionParser.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Networks/ExpressionParser.cs rename to SNP_Evolution/Model/ExpressionParser.cs index fcd9f6a..f180336 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/ExpressionParser.cs +++ b/SNP_Evolution/Model/ExpressionParser.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Recursive descent over the regex subset that can describe spike counts: literals, '.', groups, // alternation and the ?, *, +, {n}, {n,} and {n,m} quantifiers (optionally lazy, which matches the same counts). diff --git a/SNP_Evolution/SNP_Evolution/Networks/HardwareProfile.cs b/SNP_Evolution/Model/HardwareProfile.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Networks/HardwareProfile.cs rename to SNP_Evolution/Model/HardwareProfile.cs index 2b05525..c6fe98e 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/HardwareProfile.cs +++ b/SNP_Evolution/Model/HardwareProfile.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Networks; +using SnpEvolution.Model; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // The rules integrate-and-fire hardware runs exactly, a^{>=k} / a^* -> a firing or forgetting, so a network within them exports to NIR. public static class HardwareProfile diff --git a/SNP_Evolution/Model/Model.csproj b/SNP_Evolution/Model/Model.csproj new file mode 100644 index 0000000..3e03f86 --- /dev/null +++ b/SNP_Evolution/Model/Model.csproj @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SNP_Evolution/SNP_Evolution/Networks/Network.cs b/SNP_Evolution/Model/Network.cs similarity index 97% rename from SNP_Evolution/SNP_Evolution/Networks/Network.cs rename to SNP_Evolution/Model/Network.cs index 1262ea5..655da67 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/Network.cs +++ b/SNP_Evolution/Model/Network.cs @@ -3,7 +3,7 @@ using System.Linq; using Newtonsoft.Json; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { public sealed class Network { diff --git a/SNP_Evolution/SNP_Evolution/Networks/NetworkEdits.cs b/SNP_Evolution/Model/NetworkEdits.cs similarity index 96% rename from SNP_Evolution/SNP_Evolution/Networks/NetworkEdits.cs rename to SNP_Evolution/Model/NetworkEdits.cs index 74f22be..069fd77 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/NetworkEdits.cs +++ b/SNP_Evolution/Model/NetworkEdits.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using SnpEvolution.Networks; +using SnpEvolution.Model; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Structural edits that keep a network well formed: synapses stay in range, never loop back to their own // neuron, and input and output neurons are never removed. diff --git a/SNP_Evolution/SNP_Evolution/Networks/NetworkGraph.cs b/SNP_Evolution/Model/NetworkGraph.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Networks/NetworkGraph.cs rename to SNP_Evolution/Model/NetworkGraph.cs index 9350ef2..d8b97f8 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/NetworkGraph.cs +++ b/SNP_Evolution/Model/NetworkGraph.cs @@ -5,7 +5,7 @@ using System.Security; using System.Text; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Draws a network as an SN P system diagram in standalone SVG: each neuron a rounded box holding its spikes and // rules, each synapse an arrow, and arrows to and from the environment on the output and input neurons. diff --git a/SNP_Evolution/SNP_Evolution/Networks/NetworkNotation.cs b/SNP_Evolution/Model/NetworkNotation.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Networks/NetworkNotation.cs rename to SNP_Evolution/Model/NetworkNotation.cs index 5b296ea..6b5c5f7 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/NetworkNotation.cs +++ b/SNP_Evolution/Model/NetworkNotation.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Renders a network as a text table in SN P notation, with spike counts written back out as runs of 'a'. public static class NetworkNotation diff --git a/SNP_Evolution/SNP_Evolution/Networks/NetworkPage.cs b/SNP_Evolution/Model/NetworkPage.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Networks/NetworkPage.cs rename to SNP_Evolution/Model/NetworkPage.cs index 981d269..d50fad8 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/NetworkPage.cs +++ b/SNP_Evolution/Model/NetworkPage.cs @@ -2,7 +2,7 @@ using System.Net; using System.Text; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Renders a network as a standalone HTML page for exploring in a browser. CSS lays the neurons out in the same // columns as NetworkGraph, and a short script draws the synapses over them from the boxes' real positions, using diff --git a/SNP_Evolution/SNP_Evolution/Networks/Neuron.cs b/SNP_Evolution/Model/Neuron.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Networks/Neuron.cs rename to SNP_Evolution/Model/Neuron.cs index a142ede..5bb13f3 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/Neuron.cs +++ b/SNP_Evolution/Model/Neuron.cs @@ -2,7 +2,7 @@ using System.Linq; using Newtonsoft.Json; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Marks a neuron as part of a copy of a library module: which module, and which copy of it in the network. public sealed record ModuleTag(int Module, int Instance); diff --git a/SNP_Evolution/SNP_Evolution/Networks/PositionAutomaton.cs b/SNP_Evolution/Model/PositionAutomaton.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Networks/PositionAutomaton.cs rename to SNP_Evolution/Model/PositionAutomaton.cs index 253f5d2..fd13719 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/PositionAutomaton.cs +++ b/SNP_Evolution/Model/PositionAutomaton.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using static SnpEvolution.Networks.ExpressionNode; +using static SnpEvolution.Model.ExpressionNode; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // The Glushkov construction of a rule expression: every 'a' in it is a position, and reading one spike moves each // active position to those that may follow it. Each copy of a repeated body gets its own positions. diff --git a/SNP_Evolution/SNP_Evolution/Networks/ReferenceNetworks.cs b/SNP_Evolution/Model/ReferenceNetworks.cs similarity index 98% rename from SNP_Evolution/SNP_Evolution/Networks/ReferenceNetworks.cs rename to SNP_Evolution/Model/ReferenceNetworks.cs index 5574261..8939060 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/ReferenceNetworks.cs +++ b/SNP_Evolution/Model/ReferenceNetworks.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { public static class ReferenceNetworks { diff --git a/SNP_Evolution/SNP_Evolution/Networks/Rule.cs b/SNP_Evolution/Model/Rule.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Networks/Rule.cs rename to SNP_Evolution/Model/Rule.cs index fc2aa79..7105278 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/Rule.cs +++ b/SNP_Evolution/Model/Rule.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // Two rule forms share this class. A legacy rule (Consume is null) fires when the expression matches and empties // the neuron, as in the original program. A standard rule E/a^c -> a^p;d fires when the expression matches and the diff --git a/SNP_Evolution/SNP_Evolution/Networks/SpikeCondition.cs b/SNP_Evolution/Model/SpikeCondition.cs similarity index 99% rename from SNP_Evolution/SNP_Evolution/Networks/SpikeCondition.cs rename to SNP_Evolution/Model/SpikeCondition.cs index eab8a0a..ab10828 100644 --- a/SNP_Evolution/SNP_Evolution/Networks/SpikeCondition.cs +++ b/SNP_Evolution/Model/SpikeCondition.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SnpEvolution.Networks +namespace SnpEvolution.Model { // The spike counts a rule expression accepts. Expressions are regular expressions over the single letter 'a', // and such sets are always eventually periodic, so the set is stored as a lasso: a lookup table of TailLength diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/BenchmarkServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/BenchmarkServiceTests.cs index 5c64bfd..55d7c04 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/BenchmarkServiceTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/BenchmarkServiceTests.cs @@ -1,6 +1,6 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; +using SnpEvolution.Search.Fitness; namespace SnpEvolution.Tests.Application { @@ -9,24 +9,18 @@ public class BenchmarkServiceTests [Fact] public void OnlyARacingCompositionSearchReadsThePartLibrary() { - string folder = Path.Combine(Path.GetTempPath(), "snp-benchmark-" + Guid.NewGuid().ToString("N")); + using var temp = new TempFolder("snp-benchmark"); + string folder = temp.Path; var settings = new Settings { PartLibraryFolder = folder }; ISearch[] composing = { Catalog.StructuralDefault, SearchCatalog.CompositionTournament }; Assert.Null(BenchmarkService.Plan(settings, settings.BenchmarkSettings, new[] { Catalog.StructuralDefault }).Value!.PartsNote); Assert.StartsWith("Composition search builds from 0 part(s)", BenchmarkService.Plan(settings, settings.BenchmarkSettings, composing).Value!.PartsNote); - Directory.CreateDirectory(folder); - try - { - File.WriteAllText(Path.Combine(folder, "delay-2.json"), "{ not json"); + temp.Made(); + PartFixtures.WriteBrokenLibrary(folder); - Assert.Contains("delay-2.json", BenchmarkService.Plan(settings, settings.BenchmarkSettings, composing).Error); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Contains("delay-2.json", BenchmarkService.Plan(settings, settings.BenchmarkSettings, composing).Error); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/CatalogTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/CatalogTests.cs index 3f06515..752f30d 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/CatalogTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/CatalogTests.cs @@ -1,4 +1,5 @@ using SnpEvolution.Application; +using SnpEvolution.Search; namespace SnpEvolution.Tests.Application { @@ -12,5 +13,11 @@ public void AnExactNameWinsOverNamesThatOnlyContainIt() Assert.Equal(new[] { "Contract multiply" }, Catalog.Matching(names, name => name, "contract MULTIPLY")); Assert.Equal(new[] { "Contract multiply 4-bit", "Contract multiply" }, Catalog.Matching(names, name => name, "multiply")); } + + [Fact] + public void TheMenuOffersEveryGeneticAlgorithmInTheCatalog() + { + Assert.Equal(SearchCatalog.Evolution, Catalog.Algorithms); + } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/EvaluationAccountingTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/EvaluationAccountingTests.cs index 560967a..fa4e401 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/EvaluationAccountingTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/EvaluationAccountingTests.cs @@ -1,15 +1,12 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Modules; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; namespace SnpEvolution.Tests.Application { @@ -17,12 +14,31 @@ public class EvaluationAccountingTests { private const int Population = 10; + // An evaluator reuses the same case objects for every network, so trials sharing the first trial's input are one per network. + private sealed class CountingEngine : ISimulationEngine + { + private readonly ISimulationEngine inner; + private long networks; + + public CountingEngine(ISimulationEngine inner) => this.inner = inner; + + public long Networks => Interlocked.Read(ref networks); + + public EngineSupport Support => inner.Support; + + public IReadOnlyList Run(IReadOnlyList trials, SimulationOptions options, Random random) + { + Interlocked.Add(ref networks, trials.Count(trial => ReferenceEquals(trial.Input, trials[0].Input))); + return inner.Run(trials, options, random); + } + } + // A target no small network makes in a few generations, so the run stalls and the modular loop reacts. private static Settings Stalling(int generations) => new Settings { - Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34 }), + Target = new OutputTarget(TargetKind.Sequence, Sequences.Fibonacci.Take(9).ToArray()), Task = Catalog.TargetTask, - Engine = Catalog.Engines.Single(engine => engine.Name == "CPU, single thread"), + Engine = CatalogEntries.SingleThreadEngine, Repetitions = 2, PopulationSize = Population, MaxGenerations = generations, @@ -60,50 +76,84 @@ public void ARunStopsOnceItsEvaluationBudgetIsSpent() EvolutionSession.Evolve(settings, settings.SelectedTask, factory => factory.NewNetwork(), new Random(3), _ => { }, evaluations); - Assert.InRange(evaluations.Networks, 300, 300 + Population * (1 + new ModulePolicy().SideGenerations + new ModulePolicy().IncubationGenerations)); + var modules = new ModulePolicy(); + Assert.InRange(evaluations.Networks, 300, 300 + Population * (1 + modules.SideGenerations + modules.IncubationGenerations)); } + // Composition search reads its parts from the library folder, and their recorded cost is reported as paid up front. [Fact] - public void RetestsOfASolvedNetworkCountAsVerification() + public void ACompositionRunCountsTheLibrarysPartsAsAnUpFrontCost() { - var counter = new EvaluationBudget(); - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), FunctionTask.Of("n", n => n, new[] { 1, 2 }), - new SimulationOptions(40, 5, OutputTiming.Interval), 3, new Random(1), counter, EvaluationSource.SideRun); + using var temp = new TempFolder("snp-composition"); + string folder = temp.Path; + var library = new ModuleLibrary(); + library.AddPart(PartFixtures.Measured(ReferenceParts.Delay(2), new PartOrigin(1, "a test", 1234)), "a test"); + PartLibraryFiles.Save(library, folder); + Settings settings = Stalling(5); + settings.Modules = false; + settings.PartLibraryFolder = folder; + settings.Algorithm = SearchCatalog.CompositionMapElites; + var evaluations = new EvaluationBudget(); - evaluator.Evaluate(TestNetworks.Identity()); - Assert.True(evaluator.ConfirmSolved(TestNetworks.Identity()).Solved); + IGeneticAlgorithm run = EvolutionSession.Evolve(settings, settings.SelectedTask, factory => factory.NewNetwork(), new Random(3), _ => { }, evaluations); - Assert.Equal(1, counter[EvaluationSource.SideRun]); - Assert.Equal(3, counter[EvaluationSource.Verification]); + Assert.Equal(1234, evaluations.UpFront); + Assert.Equal(5L * Population, evaluations.Networks); + Assert.Contains(run.Population, individual => individual.Genes.Neurons.Any(neuron => neuron.Module != null)); + Assert.Contains("1,234", evaluations.Report().Describe()); } - // Composition search reads its parts from the library folder, and their recorded cost is reported as paid up front. [Fact] - public void ACompositionRunCountsTheLibrarysPartsAsAnUpFrontCost() + [Slow] + public void EveryNetworkAModularRunScoresIsCharged() { - string folder = Path.Combine(Path.GetTempPath(), "snp-composition-" + Guid.NewGuid()); - try - { - var library = new ModuleLibrary(); - library.AddPart(Verifier.Measure(ReferenceParts.Delay(2), new EvaluationBudget()).ToLibraryPart(ReferenceParts.Delay(2), new PartOrigin(1, "a test", 1234)), "a test"); - PartLibraryFiles.Save(library, folder); - Settings settings = Stalling(5); - settings.Modules = false; - settings.PartLibraryFolder = folder; - settings.Algorithm = SearchCatalog.CompositionMapElites; - var evaluations = new EvaluationBudget(); - - IGeneticAlgorithm run = EvolutionSession.Evolve(settings, settings.SelectedTask, factory => factory.NewNetwork(), new Random(3), _ => { }, evaluations); - - Assert.Equal(1234, evaluations.UpFront); - Assert.Equal(5L * Population, evaluations.Networks); - Assert.Contains(run.Population, individual => individual.Genes.Neurons.Any(neuron => neuron.Module != null)); - Assert.Contains("1,234", evaluations.Report().Describe()); - } - finally + var engine = new CountingEngine(new SequentialCpuEngine()); + var settings = new Settings { - Directory.Delete(folder, recursive: true); - } + Target = new OutputTarget(TargetKind.Sequence, Sequences.Fibonacci.Take(9).ToArray()), + Task = Catalog.TargetTask, + Engine = new CatalogEntry("counting", _ => engine), + Repetitions = 2, + PopulationSize = 10, + MaxGenerations = 60, + IterativeEvolution = false, + StagnationPatience = 10, + Modules = true, + }; + var budget = new EvaluationBudget(); + + EvolutionSession.Evolve(settings, settings.SelectedTask, factory => factory.NewNetwork(), new Random(3), _ => { }, budget); + + Assert.True(budget[EvaluationSource.SideRun] > 0); + Assert.Equal(engine.Networks, budget.Networks); + } + + [Fact] + [Slow] + public void EveryNetworkABenchmarkRunScoresIsChargedRetestsIncluded() + { + var engine = new CountingEngine(new ExhaustiveCpuEngine()); + BenchmarkSettings settings = BenchmarkSettings.Default with { Seeds = 1, CreateEngine = () => engine }; + + RunOutcome outcome = Benchmark.RunOnce(Catalog.StructuralDefault, TaskSuite.Functions.First(task => task.Name == "Compute n"), seed: 1, budget: 4000, settings); + + Assert.True(outcome.Solved); + Assert.Equal(engine.Networks, outcome.Evaluations); + } + + [Fact] + [Slow] + public void EveryNetworkAPartSearchScoresIsChargedAndItsChecksBesideThem() + { + var engine = new CountingEngine(new ExhaustiveCpuEngine()); + var settings = new PartSearchSettings(5_000, 1_000, 30, Catalog.StructuralDefault, () => engine); + + PartOutcome outcome = PartSearch.Evolve(PartFixtures.DelayContract(2), 1, settings, new EvaluationBudget(), _ => { }); + + Assert.True(outcome.Solved); + Assert.Equal(engine.Networks, outcome.Spent.Networks); + Assert.True(outcome.Spent[EvaluationKind.ExhaustiveCheck] > 0); + Assert.Equal(outcome.Spent.Networks + outcome.Spent[EvaluationKind.ExhaustiveCheck], outcome.Evaluations); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/ComposeSessionTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/EvolutionSessionTests.cs similarity index 59% rename from SNP_Evolution/SNP_Evolution.Tests/Application/ComposeSessionTests.cs rename to SNP_Evolution/SNP_Evolution.Tests/Application/EvolutionSessionTests.cs index 6023f56..d5a85f6 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/ComposeSessionTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/EvolutionSessionTests.cs @@ -1,39 +1,34 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Genome; using SnpEvolution.Simulation; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Tasks; using SnpEvolution.Storage; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; +using static SnpEvolution.Tests.Fixtures.ModuleFixtures; namespace SnpEvolution.Tests.Application { - public sealed class ComposeSessionTests : IDisposable + public sealed class EvolutionSessionTests : IDisposable { - private readonly string folder = Path.Combine(Path.GetTempPath(), "snp-compose-" + Guid.NewGuid().ToString("N")); + private readonly TempFolder temp = new TempFolder("snp-compose"); private readonly List log = new List(); - public void Dispose() - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } + private string folder => temp.Path; + + public void Dispose() => temp.Dispose(); private Settings Composing(CatalogEntry task) => new Settings { Task = task, RuleForm = RuleForm.Standard, OutputTiming = OutputTiming.Interval, - Engine = Catalog.Engines.Single(engine => engine.Name == "CPU, single thread"), + Engine = CatalogEntries.SingleThreadEngine, Algorithm = SearchCatalog.CompositionMapElites, Repetitions = 2, PopulationSize = 40, @@ -47,7 +42,19 @@ private IGeneticAlgorithm Run(Settings settings, int seed, EvaluationBudget? eva private static CatalogEntry SuiteTask(string name) => Catalog.Tasks.Single(task => task.Name == name); - // The done-when through the session the compose command runs: solved, promoted, saved, and the add loop reported as reused. + private Settings OneIncrementTowardsAGap() + { + var library = new ModuleLibrary(); + library.AddPart(PartFixtures.Verified(ReferenceParts.Increment()), "a test"); + PartLibraryFiles.Save(library, Path.Combine(folder, "parts")); + Settings settings = Composing(Catalog.TargetTask); + settings.Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 6, 1 }); + settings.IterativeEvolution = false; + settings.PopulationSize = 12; + settings.StagnationPatience = 4; + return settings; + } + [Fact] [Slow] public void ASolvedContractIsPromotedSavedAndItsReuseReported() @@ -72,15 +79,8 @@ public void ASolvedContractIsPromotedSavedAndItsReuseReported() [Slow] public void TheRunLogListsEachProposalWithItsOutcome() { - var library = new ModuleLibrary(); - library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - PartLibraryFiles.Save(library, Path.Combine(folder, "parts")); - Settings settings = Composing(Catalog.TargetTask); - settings.Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 6, 1 }); - settings.IterativeEvolution = false; - settings.PopulationSize = 12; + Settings settings = OneIncrementTowardsAGap(); settings.MaxGenerations = 40; - settings.StagnationPatience = 4; settings.ProposalBudget = 5_000; var evaluations = new EvaluationBudget(); @@ -93,51 +93,28 @@ public void TheRunLogListsEachProposalWithItsOutcome() Assert.Matches(@"generation \d+: delay \d+ \(failing checks, nothing passes gap \d+ \(\d+\)\): (solved in \d+ evaluations, kept as module \d+|not solved)", parts); } - [Fact] - public void AContractRunJustShortOfAPerfectScoreIsNotSolved() - { - var individual = new Individual(TestNetworks.AlwaysOutputsOne()); - individual.Record(new FitnessResult(0.99f, Array.Empty(), "", Exact: true)); - var run = new FixedPopulation(individual); - - Assert.True(RunLayers.IsSolved(run)); - Assert.False(RunLayers.IsSolved(run, new ContractTask(PartFixtures.RegisterContract()))); - } - - private sealed class FixedPopulation(Individual best) : IGeneticAlgorithm - { - public IReadOnlyList Population => new[] { best }; - - public int Generation => 1; - - public Individual? Best => best; - - public IReadOnlyList> FitnessHistory => Array.Empty>(); - - public void NextGeneration() { } - - public void Immigrate(IReadOnlyList newcomers) { } - - public void Rescore() { } - } - [Fact] public void ProposalsCanBeTurnedOff() { - var library = new ModuleLibrary(); - library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - PartLibraryFiles.Save(library, Path.Combine(folder, "parts")); - Settings settings = Composing(Catalog.TargetTask); - settings.Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 6, 1 }); - settings.IterativeEvolution = false; - settings.PopulationSize = 12; + Settings settings = OneIncrementTowardsAGap(); settings.MaxGenerations = 20; - settings.StagnationPatience = 4; settings.ProposeParts = false; IGeneticAlgorithm run = Run(settings, seed: 3); Assert.Empty(RunLayers.Proposals(run)!.Proposals); } + + [Fact] + public void OnlyOrderedTargetsEvolveIteratively() + { + var sequence = new Settings { Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2, 3, 5, 8 }), Task = Catalog.TargetTask }; + var set = new Settings { Target = OutputTarget.Set(new[] { 2, 4, 6, 8 }), Task = Catalog.TargetTask }; + + Assert.True(EvolutionSession.IsIterative(sequence, sequence.SelectedTask, out _)); + Assert.False(EvolutionSession.IsIterative(set, set.SelectedTask, out _)); + sequence.IterativeEvolution = false; + Assert.False(EvolutionSession.IsIterative(sequence, sequence.SelectedTask, out _)); + } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/EvolveServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/EvolveServiceTests.cs index 08bd60a..ffb94f4 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/EvolveServiceTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/EvolveServiceTests.cs @@ -1,5 +1,5 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Search; +using SnpEvolution.Search; namespace SnpEvolution.Tests.Application { @@ -8,22 +8,15 @@ public class EvolveServiceTests [Fact] public void ACompositionRunOnABrokenLibraryIsATypedErrorNamingTheFile() { - string folder = Path.Combine(Path.GetTempPath(), "snp-evolve-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(folder); - try - { - File.WriteAllText(Path.Combine(folder, "delay-2.json"), "{ not json"); - var settings = new Settings { Algorithm = SearchCatalog.CompositionTournament, PartLibraryFolder = folder }; + using var temp = new TempFolder("snp-evolve").Made(); + string folder = temp.Path; + PartFixtures.WriteBrokenLibrary(folder); + var settings = new Settings { Algorithm = SearchCatalog.CompositionTournament, PartLibraryFolder = folder }; - EvolveResult result = EvolveService.Run(new EvolveRequest(settings, new Random(1), "Composed", Folder: folder), _ => { }); + EvolveResult result = EvolveService.Run(new EvolveRequest(settings, new Random(1), "Composed", Folder: folder), _ => { }); - Assert.Null(result.Run); - Assert.Contains("delay-2.json", result.Error); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Null(result.Run); + Assert.Contains("delay-2.json", result.Error); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/ExportServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/ExportServiceTests.cs new file mode 100644 index 0000000..cd876a4 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/ExportServiceTests.cs @@ -0,0 +1,18 @@ +using SnpEvolution.Application; +using SnpEvolution.Storage; + +namespace SnpEvolution.Tests.Application +{ + public class ExportServiceTests + { + [Fact] + public void AnExportedNetworkRunsForTheStepsGiven() + { + using var temp = new TempFolder("snp-network").Made(); + string file = Path.Combine(temp.Path, "network.json"); + NetworkFiles.Save(TestNetworks.PingPong(), file); + + Assert.Equal(7, Assert.Single(ExportService.Network(file, 7).Value!.Cases).Steps); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/NetworkRunServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/NetworkRunServiceTests.cs index 118a765..d1a7b9a 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/NetworkRunServiceTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/NetworkRunServiceTests.cs @@ -4,7 +4,7 @@ namespace SnpEvolution.Tests.Application { public class NetworkRunServiceTests { - private static Settings SingleThread() => new Settings { Engine = Catalog.Engines.Single(engine => engine.Name == "CPU, single thread") }; + private static Settings SingleThread() => new Settings { Engine = CatalogEntries.SingleThreadEngine }; [Fact] public void ANetworkWithAnInputIsScoredOnTheSelectedTask() diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/OutputTargetTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/OutputTargetTests.cs new file mode 100644 index 0000000..7b4a6eb --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/OutputTargetTests.cs @@ -0,0 +1,48 @@ +using SnpEvolution.Application; +using SnpEvolution.Specs.Tasks; + +namespace SnpEvolution.Tests.Application +{ + public class OutputTargetTests + { + [Theory] + [InlineData("1,1,2,3,5,8,13", new[] { 1, 1, 2, 3, 5, 8, 13 })] + [InlineData("1, 1, 2, 3, 5 ...", new[] { 1, 1, 2, 3, 5 })] + [InlineData("{2 4 6}", new[] { 2, 4, 6 })] + [InlineData("[3,1,4]…", new[] { 3, 1, 4 })] + public void NumbersParseWithAnySeparatorsBracketsAndEllipsis(string input, int[] expected) + { + Assert.True(OutputTarget.TryParse(TargetKind.Sequence, input, out OutputTarget target)); + Assert.Equal(expected, target.Values); + } + + [Theory] + [InlineData("")] + [InlineData("1,0,2")] + [InlineData("1,-2")] + [InlineData("1,x")] + public void NumbersMustBePositiveIntegers(string input) + { + Assert.False(OutputTarget.TryParse(TargetKind.Set, input, out _)); + } + + [Fact] + public void BinaryWordsIgnoreSpacingAndNeedAOne() + { + Assert.True(OutputTarget.TryParse(TargetKind.BinaryWord, "0110 1001_0", out OutputTarget target)); + Assert.Equal(new[] { 0, 1, 1, 0, 1, 0, 0, 1, 0 }, target.Values); + Assert.False(OutputTarget.TryParse(TargetKind.BinaryWord, "0000", out _)); + Assert.False(OutputTarget.TryParse(TargetKind.BinaryWord, "0120", out _)); + } + + [Fact] + public void EachKindMakesItsTask() + { + var fitness = new JaccardFitness(new[] { 1 }); + + Assert.IsType(new OutputTarget(TargetKind.Set, new[] { 1, 1, 2 }).CreateTask(fitness)); + Assert.IsType(new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2 }).CreateTask(fitness)); + Assert.IsType(new OutputTarget(TargetKind.BinaryWord, new[] { 0, 1 }).CreateTask(fitness)); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/PartLibrariesTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/PartLibrariesTests.cs index e73f4e4..2c6af1f 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/PartLibrariesTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/PartLibrariesTests.cs @@ -1,5 +1,5 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Parts; +using SnpEvolution.Specs.Parts; namespace SnpEvolution.Tests.Application { @@ -9,7 +9,8 @@ public class PartLibrariesTests [Slow] public void HandBuiltLeavesLeaveOutThePromotedAddLoop() { - string missing = Path.Combine(Path.GetTempPath(), "snp-no-parts-" + Guid.NewGuid().ToString("N")); + using var temp = new TempFolder("snp-no-parts"); + string missing = temp.Path; ModuleLibrary leaves = PartLibraries.Load(missing, handBuilt: true, addLoop: false).Value!; ModuleLibrary withLoop = PartLibraries.Load(missing, handBuilt: true, addLoop: true).Value!; @@ -22,21 +23,14 @@ public void HandBuiltLeavesLeaveOutThePromotedAddLoop() [Fact] public void ABrokenLibraryIsATypedErrorNamingTheFile() { - string folder = Path.Combine(Path.GetTempPath(), "snp-broken-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(folder); - try - { - File.WriteAllText(Path.Combine(folder, "delay-2.json"), "{ not json"); + using var temp = new TempFolder("snp-broken").Made(); + string folder = temp.Path; + PartFixtures.WriteBrokenLibrary(folder); - Loaded loaded = PartLibraries.Load(folder, handBuilt: false, addLoop: false); + Loaded loaded = PartLibraries.Load(folder, handBuilt: false, addLoop: false); - Assert.Null(loaded.Value); - Assert.Contains("delay-2.json", loaded.Error); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Null(loaded.Value); + Assert.Contains("delay-2.json", loaded.Error); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/PartsServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/PartsServiceTests.cs index 869108c..85958fa 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/PartsServiceTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/PartsServiceTests.cs @@ -1,32 +1,25 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Contracts; +using SnpEvolution.Specs.Contracts; using SnpEvolution.Storage; namespace SnpEvolution.Tests.Application { public sealed class PartsServiceTests : IDisposable { - private readonly string folder = Path.Combine(Path.GetTempPath(), "parts-session-" + Guid.NewGuid().ToString("N")); + private static readonly IReadOnlyList Delays = new[] { FirstParts.Named("delay 2"), FirstParts.Named("delay 4") }; - public void Dispose() - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } + private readonly TempFolder temp = new TempFolder("parts-session"); - private static readonly IReadOnlyList Delays = new[] { FirstParts.Named("delay 2"), FirstParts.Named("delay 4") }; + private string folder => temp.Path; + + public void Dispose() => temp.Dispose(); private Settings Library(string library) => new Settings { PartBudget = 4000, PartLibraryFolder = Path.Combine(folder, library) }; private static PartsRequest Request(bool redo = false) => new PartsRequest(Delays, Seed: 1, Redo: redo); - private int Run(string library, Action log, bool redo = false) - { - PartsResult result = PartsService.Run(Library(library), Request(redo), log); - return result.Error != null ? 1 : result.AllSolved ? 0 : 2; - } + private PartsResult Run(string library, Action log, bool redo = false) => + PartsService.Run(Library(library), Request(redo), log); private static Dictionary Files(string library) => Directory.GetFiles(library).ToDictionary(path => Path.GetFileName(path), File.ReadAllText); @@ -37,8 +30,13 @@ public void TheSameSeedWritesTheSameLibrary() { var log = new List(); - Assert.Equal(0, Run("first", log.Add)); - Assert.Equal(0, Run("second", _ => { })); + PartsResult firstRun = Run("first", log.Add); + PartsResult secondRun = Run("second", _ => { }); + + Assert.Null(firstRun.Error); + Assert.True(firstRun.AllSolved); + Assert.Null(secondRun.Error); + Assert.True(secondRun.AllSolved); Dictionary first = Files(Path.Combine(folder, "first")); Assert.Equal(new[] { "delay-2.json", "delay-4.json" }, first.Keys.Order()); @@ -77,7 +75,7 @@ public void ABrokenLibraryIsReportedAndNothingIsEvolved() { string library = Path.Combine(folder, "broken"); Directory.CreateDirectory(library); - File.WriteAllText(Path.Combine(library, "delay-2.json"), "{ not json"); + PartFixtures.WriteBrokenLibrary(library); PartsResult result = PartsService.Run(Library("broken"), Request(), _ => { }); diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/ReachServiceTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/ReachServiceTests.cs index ce937b1..20f98b5 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/ReachServiceTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/ReachServiceTests.cs @@ -1,7 +1,5 @@ using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; namespace SnpEvolution.Tests.Application @@ -13,7 +11,7 @@ public class ReachServiceTests private static Settings Budgeted(string folder, long budget) => new Settings { - Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2, 3, 5, 8, 13 }), + Target = new OutputTarget(TargetKind.Sequence, Sequences.Fibonacci.Take(7).ToArray()), Task = Catalog.TargetTask, Repetitions = 3, PopulationSize = Population, @@ -41,25 +39,19 @@ public void WithoutABudgetNothingRunsSinceTheSetupsAreComparedOnIt() => [Slow] public void CompositionSearchSpendsOnlyWhatThePartsLeaveOfTheSharedBudget() { - string folder = Path.Combine(Path.GetTempPath(), "snp-reach-" + Guid.NewGuid()); - try - { - var library = new ModuleLibrary(); - library.AddPart(Verifier.Measure(ReferenceParts.Delay(2), new EvaluationBudget()).ToLibraryPart(ReferenceParts.Delay(2), new PartOrigin(1, "a test", PartCost)), "a test"); - PartLibraryFiles.Save(library, folder); - Settings settings = Budgeted(folder, PartCost + 100); - - ReachService.Outcome composition = ReachService.RunOnce(settings, Named("composition"), 1, PartCost); - ReachService.Outcome flat = ReachService.RunOnce(settings, Named("flat"), 1, PartCost); - - Assert.Equal(PartCost, composition.UpFront); - Assert.InRange(composition.Evaluations, 100, PartCost - 1); - Assert.True(flat.Evaluations >= PartCost + 100); - } - finally - { - Directory.Delete(folder, recursive: true); - } + using var temp = new TempFolder("snp-reach"); + string folder = temp.Path; + var library = new ModuleLibrary(); + library.AddPart(PartFixtures.Measured(ReferenceParts.Delay(2), new PartOrigin(1, "a test", PartCost)), "a test"); + PartLibraryFiles.Save(library, folder); + Settings settings = Budgeted(folder, PartCost + 100); + + ReachService.Outcome composition = ReachService.RunOnce(settings, Named("composition"), 1, PartCost); + ReachService.Outcome flat = ReachService.RunOnce(settings, Named("flat"), 1, PartCost); + + Assert.Equal(PartCost, composition.UpFront); + Assert.InRange(composition.Evaluations, 100, PartCost - 1); + Assert.True(flat.Evaluations >= PartCost + 100); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/RunAdvisorTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/RunAdvisorTests.cs index 1e2926b..fdf39b9 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/RunAdvisorTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/RunAdvisorTests.cs @@ -12,7 +12,7 @@ private static Settings ForSequence(params int[] values) => [Fact] public void SuggestsFixingAFibonacciTypo() { - Settings settings = ForSequence(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 114, 233); + Settings settings = ForSequence(Sequences.FibonacciWithTypo.Take(13).ToArray()); Suggestion? typo = For(RunAdvisor.Advise(settings), "Target"); typo!.Apply(settings); @@ -36,7 +36,7 @@ public void SuggestsAUsefulPopulationAndIterativeEvolution() [Fact] public void HasNothingMoreToSayOnceItsAdviceIsTaken() { - Settings settings = ForSequence(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 114, 233); + Settings settings = ForSequence(Sequences.FibonacciWithTypo.Take(13).ToArray()); settings.PopulationSize = 4; settings.MaxNeurons = 100; settings.StagnationRecovery = false; @@ -69,17 +69,5 @@ public void AdvisingDoesNotChangeTheSettings() Assert.Equal(4, settings.PopulationSize); } - - [Fact] - public void OnlyOrderedTargetsEvolveIteratively() - { - Settings sequence = ForSequence(1, 1, 2, 3, 5, 8); - var set = new Settings { Target = OutputTarget.Set(new[] { 2, 4, 6, 8 }), Task = Catalog.TargetTask }; - - Assert.True(EvolutionSession.IsIterative(sequence, sequence.SelectedTask, out _)); - Assert.False(EvolutionSession.IsIterative(set, set.SelectedTask, out _)); - sequence.IterativeEvolution = false; - Assert.False(EvolutionSession.IsIterative(sequence, sequence.SelectedTask, out _)); - } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/RunFoldersTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/RunFoldersTests.cs index 4fca44a..f94032e 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Application/RunFoldersTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/RunFoldersTests.cs @@ -2,26 +2,21 @@ namespace SnpEvolution.Tests.Application { - public class WorkingRootTests + public class RunFoldersTests { [Fact] public void AGitFolderOrAWorktreesGitFileMarksTheRoot() { - string folder = Path.Combine(Path.GetTempPath(), "snp-root-" + Guid.NewGuid().ToString("N")); + using var temp = new TempFolder("snp-root"); + string folder = temp.Path; string inside = Path.Combine(folder, "repository", "worktree", "deep"); Directory.CreateDirectory(inside); - try - { - Directory.CreateDirectory(Path.Combine(folder, "repository", ".git")); - Assert.Equal(Path.Combine(folder, "repository"), RunFolders.RepositoryRootAbove(inside)); - File.WriteAllText(Path.Combine(folder, "repository", "worktree", ".git"), "gitdir: elsewhere"); - Assert.Equal(Path.Combine(folder, "repository", "worktree"), RunFolders.RepositoryRootAbove(inside)); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Directory.CreateDirectory(Path.Combine(folder, "repository", ".git")); + Assert.Equal(Path.Combine(folder, "repository"), RunFolders.RepositoryRootAbove(inside)); + + File.WriteAllText(Path.Combine(folder, "repository", "worktree", ".git"), "gitdir: elsewhere"); + Assert.Equal(Path.Combine(folder, "repository", "worktree"), RunFolders.RepositoryRootAbove(inside)); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/RunLayersTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/RunLayersTests.cs new file mode 100644 index 0000000..c6ccc16 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/RunLayersTests.cs @@ -0,0 +1,41 @@ +using SnpEvolution.Application; +using SnpEvolution.Model; +using SnpEvolution.Search; +using SnpEvolution.Search.Algorithms; +using SnpEvolution.Search.Fitness; +using SnpEvolution.Search.Genome; +using SnpEvolution.Specs.Tasks; + +namespace SnpEvolution.Tests.Application +{ + public class RunLayersTests + { + [Fact] + public void AContractRunJustShortOfAPerfectScoreIsNotSolved() + { + var individual = new Individual(TestNetworks.AlwaysOutputsOne()); + individual.Record(new FitnessResult(0.99f, Array.Empty(), "", Exact: true)); + var run = new FixedPopulation(individual); + + Assert.True(RunLayers.IsSolved(run)); + Assert.False(RunLayers.IsSolved(run, new ContractTask(PartFixtures.RegisterContract()))); + } + + private sealed class FixedPopulation(Individual best) : IGeneticAlgorithm + { + public IReadOnlyList Population => new[] { best }; + + public int Generation => 1; + + public Individual? Best => best; + + public IReadOnlyList> FitnessHistory => Array.Empty>(); + + public void NextGeneration() { } + + public void Immigrate(IReadOnlyList newcomers) { } + + public void Rescore() { } + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Application/TargetPatternsTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Application/TargetPatternsTests.cs new file mode 100644 index 0000000..1754756 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Application/TargetPatternsTests.cs @@ -0,0 +1,36 @@ +using SnpEvolution.Application; + +namespace SnpEvolution.Tests.Application +{ + public class TargetPatternsTests + { + [Fact] + public void FindsTheTypoInAFibonacciTarget() + { + SuspectedTypo? typo = TargetPatterns.Find(Sequences.FibonacciWithTypo.Take(16).ToArray()); + + Assert.NotNull(typo); + Assert.Equal((11, 114, 144), (typo!.Index, typo.Found, typo.Expected)); + } + + [Theory] + [InlineData(new[] { 2, 4, 6, 9, 10 }, 3, 8)] + [InlineData(new[] { 1, 2, 4, 8, 15, 32 }, 4, 16)] + [InlineData(new[] { 7, 1, 2, 3, 5, 8 }, 0, 1)] + public void FindsASingleValueThatBreaksAPattern(int[] values, int index, int expected) + { + SuspectedTypo? typo = TargetPatterns.Find(values); + + Assert.Equal((index, expected), (typo!.Index, typo.Expected)); + } + + [Theory] + [InlineData(new[] { 1, 1, 2, 3, 5, 8, 13 })] + [InlineData(new[] { 3, 1, 4, 1, 5, 9 })] + [InlineData(new[] { 1, 1, 2, 4 })] + public void LeavesTargetsWithoutAClearTypoAlone(int[] values) + { + Assert.Null(TargetPatterns.Find(values)); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/BenchmarkCommandTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/BenchmarkCommandTests.cs new file mode 100644 index 0000000..7d8bbdd --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/BenchmarkCommandTests.cs @@ -0,0 +1,20 @@ +using SnpEvolution.Search; + +namespace SnpEvolution.Tests.Cli +{ + [Collection(ProcessStateCollection.Name)] + public class BenchmarkCommandTests + { + [Fact] + public void ABenchmarkOfCompositionSearchBuildsFromThePartLibrary() + { + using var run = new CommandRun(); + run.CopyFolder("parts"); + + string output = run.Run("benchmark", "--algorithm", SearchCatalog.CompositionMapElites.Name, "--task", "Compute n", "--seeds", "1", "--budget", "20", + "--population", "5", "--library", "parts"); + + Assert.Contains("Composition search builds from", output); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/CommandArgsTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/CommandArgsTests.cs index c49f0d3..e55a9c2 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Cli/CommandArgsTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/CommandArgsTests.cs @@ -1,9 +1,10 @@ using SnpEvolution.Application; using SnpEvolution.Cli; -using SnpEvolution.Evolution.Modules; +using SnpEvolution.Search.Modules; namespace SnpEvolution.Tests.Cli { + [Collection(ProcessStateCollection.Name)] public class CommandArgsTests { private static string Problem(params string[] line) @@ -13,13 +14,12 @@ private static string Problem(params string[] line) return error; } + private static CommandArgs Parsed(params string[] line) => + CommandArgs.Parse(CommandRegistry.Find(line[0])!, line, out string error) ?? throw new ArgumentException(error); + // The settings evolve would run with, from the menu's defaults and the options given. - private static Settings Evolving(params string[] options) - { - string[] line = new[] { "evolve", "--target", "1,2" }.Concat(options).ToArray(); - CommandArgs args = CommandArgs.Parse(CommandRegistry.Find("evolve")!, line, out string error) ?? throw new ArgumentException(error); - return TargetArgs.Settings(args, SettingOptions.Evolve)!; - } + private static Settings Evolving(params string[] options) => + TargetArgs.Settings(Parsed(new[] { "evolve", "--target", "1,2" }.Concat(options).ToArray()), SettingOptions.Evolve)!; [Fact] public void AnUnknownOptionIsRefusedByName() => @@ -59,8 +59,12 @@ public void AWordThatIsNotAnOptionIsRefused() => [InlineData("evolve", "--target", "1,2", "--glue-weight", "-1")] [InlineData("compose", "--task", "no such task")] [InlineData("evolve", "--target", "1,2", "--algorithm", "no such search")] - public void BadCommandLinesExitWithUsage(params string[] line) => + public void BadCommandLinesExitWithUsage(params string[] line) + { + using var console = new ConsoleCapture(); + Assert.Equal((int)ExitCode.Usage, CommandLine.Run(line)); + } [Fact] public void BudgetAndCompositionOptionsReachTheSettings() @@ -103,8 +107,7 @@ public void ModuleFilesTurnModulesOn() [Fact] public void OptionsGivenOverrideTheAdvisorsSuggestions() { - string[] line = { "evolve", "--target", "1,2", "--advise", "on", "--population", "3" }; - CommandArgs args = CommandArgs.Parse(CommandRegistry.Find("evolve")!, line, out string error) ?? throw new ArgumentException(error); + CommandArgs args = Parsed("evolve", "--target", "1,2", "--advise", "on", "--population", "3"); Assert.Equal(3, EvolveCommand.Configured(args, _ => { })!.PopulationSize); } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/EvolvePartsCommandTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/EvolvePartsCommandTests.cs new file mode 100644 index 0000000..9adc477 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/EvolvePartsCommandTests.cs @@ -0,0 +1,26 @@ +using SnpEvolution.Cli; +using SnpEvolution.Model; +using SnpEvolution.Storage; + +namespace SnpEvolution.Tests.Cli +{ + [Collection(ProcessStateCollection.Name)] + public class EvolvePartsCommandTests + { + [Fact] + [Slow] + public void EvolvePartsUnderTheProfileSavesAProfilePart() + { + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-export"); + string folder = temp.Path; + + int exit = CommandLine.Run(new[] { "evolve-parts", "--only", "delay 1", "--budget", "5000", "--profile", "hardware", "--library", folder }); + + Assert.Equal((int)ExitCode.Success, exit); + var part = PartLibraryFiles.Read(File.ReadAllText(Path.Combine(folder, "delay-1.json")), "delay-1.json"); + Assert.Empty(HardwareProfile.Problems(part.Part.Network)); + Assert.EndsWith("--profile hardware", part.Origin.Run); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/ExportCommandsTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/ExportCommandsTests.cs index 48bb6d3..7619063 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Cli/ExportCommandsTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/ExportCommandsTests.cs @@ -1,87 +1,52 @@ using SnpEvolution.Application; using SnpEvolution.Cli; -using SnpEvolution.Storage; namespace SnpEvolution.Tests.Cli { + [Collection(ProcessStateCollection.Name)] public class ExportCommandsTests { - private static string Folder() => Path.Combine(Path.GetTempPath(), "snp-export-" + Guid.NewGuid().ToString("N")); + private static readonly string Delay2 = RepositoryFiles.PartFile("parts", "delay-2.json"); [Fact] public void ExportVerilogWritesTheDesignTestbenchAndExpectedTrace() { - string folder = Folder(); - try - { - int exit = CommandLine.Run(new[] { "export-verilog", "--part", RepositoryFiles.PartFile("parts", "delay-2.json"), "--out", folder, "--check", "off" }); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-export"); + string folder = temp.Path; - Assert.Equal(0, exit); - Assert.All(new[] { "delay_2.v", "delay_2_tb.v", "delay_2_expected.txt" }, file => Assert.True(File.Exists(Path.Combine(folder, file)), file)); - } - finally - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } + int exit = CommandLine.Run(new[] { "export-verilog", "--part", Delay2, "--out", folder, "--check", "off" }); + + Assert.Equal((int)ExitCode.Success, exit); + Assert.All(new[] { "delay_2.v", "delay_2_tb.v", "delay_2_expected.txt" }, file => Assert.True(File.Exists(Path.Combine(folder, file)), file)); } [Fact] public void ExportUppaalWritesTheModelAndItsQueries() { - string folder = Folder(); - try - { - int exit = CommandLine.Run(new[] { "export-uppaal", "--part", RepositoryFiles.PartFile("parts", "delay-2.json"), "--out", folder, "--check", "off" }); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-export"); + string folder = temp.Path; + + int exit = CommandLine.Run(new[] { "export-uppaal", "--part", Delay2, "--out", folder, "--check", "off" }); - Assert.Equal(0, exit); - Assert.All(new[] { "delay-2.xml", "delay-2.q" }, file => Assert.True(File.Exists(Path.Combine(folder, file)), file)); - } - finally - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } + Assert.Equal((int)ExitCode.Success, exit); + Assert.All(new[] { "delay-2.xml", "delay-2.q" }, file => Assert.True(File.Exists(Path.Combine(folder, file)), file)); } [Fact] public void ExportNirRefusesAPartOutsideTheProfile() { - string folder = Folder(); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-export"); + string folder = temp.Path; - int exit = CommandLine.Run(new[] { "export-nir", "--part", RepositoryFiles.PartFile("parts", "delay-2.json"), "--out", folder }); + int exit = CommandLine.Run(new[] { "export-nir", "--part", Delay2, "--out", folder }); - Assert.Equal(1, exit); + Assert.Equal((int)ExitCode.Usage, exit); Assert.False(Directory.Exists(folder)); } - [Fact] - [Slow] - public void EvolvePartsUnderTheProfileSavesAProfilePart() - { - string folder = Folder(); - try - { - int exit = CommandLine.Run(new[] { "evolve-parts", "--only", "delay 1", "--budget", "5000", "--profile", "hardware", "--library", folder }); - - Assert.Equal(0, exit); - var part = SnpEvolution.Storage.PartLibraryFiles.Read(File.ReadAllText(Path.Combine(folder, "delay-1.json")), "delay-1.json"); - Assert.Empty(SnpEvolution.Networks.HardwareProfile.Problems(part.Part.Network)); - Assert.EndsWith("--profile hardware", part.Origin.Run); - } - finally - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } - } - [Fact] public void ACheckThatDiffersFromOurEngineHasItsOwnExitCode() { @@ -90,26 +55,13 @@ public void ACheckThatDiffersFromOurEngineHasItsOwnExitCode() Assert.Equal(ExitCode.Differs, ExportCommand.ExitFor(ExportStatus.Differs)); } - [Fact] - public void AnExportedNetworkRunsForTheStepsGiven() - { - string file = Path.Combine(Path.GetTempPath(), "snp-network-" + Guid.NewGuid().ToString("N") + ".json"); - try - { - NetworkFiles.Save(TestNetworks.PingPong(), file); - - Assert.Equal(7, Assert.Single(ExportService.Network(file, 7).Value!.Cases).Steps); - } - finally - { - File.Delete(file); - } - } - [Fact] public void ExportNeedsAPartOrANetwork() { - Assert.Equal(1, CommandLine.Run(new[] { "export-verilog", "--out", Folder() })); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-export"); + + Assert.Equal((int)ExitCode.Usage, CommandLine.Run(new[] { "export-verilog", "--out", temp.Path })); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/ListCommandsTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/ListCommandsTests.cs index 4394177..972b549 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Cli/ListCommandsTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/ListCommandsTests.cs @@ -1,8 +1,8 @@ -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Tests.Golden; +using SnpEvolution.Search.Benchmarking; namespace SnpEvolution.Tests.Cli { + [Collection(ProcessStateCollection.Name)] public class ListCommandsTests { [Fact] diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/SavedRunsTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/SavedRunsTests.cs index 5c56ca5..97363fe 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Cli/SavedRunsTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/SavedRunsTests.cs @@ -6,17 +6,11 @@ namespace SnpEvolution.Tests.Cli { public class SavedRunsTests : IDisposable { - private readonly string folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + private readonly TempFolder folder = new TempFolder("snp-saved-runs"); - private SavedRuns Store => new SavedRuns(Path.Combine(folder, "saved-runs.json")); + private SavedRuns Store => new SavedRuns(Path.Combine(folder.Path, "saved-runs.json")); - public void Dispose() - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } + public void Dispose() => folder.Dispose(); private static SavedRun Run(string name, Settings settings) => new SavedRun(name, DateTime.Now, RunStart.Scratch, "TargetNet", "solved", settings); diff --git a/SNP_Evolution/SNP_Evolution.Tests/Cli/VerifyCommandTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Cli/VerifyCommandTests.cs index 0eb8314..d62bff6 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Cli/VerifyCommandTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Cli/VerifyCommandTests.cs @@ -1,86 +1,62 @@ using SnpEvolution.Cli; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; -using SnpEvolution.Tests.Evolution; namespace SnpEvolution.Tests.Cli { + [Collection(ProcessStateCollection.Name)] public class VerifyCommandTests { - private static string Folder() => Path.Combine(Path.GetTempPath(), "snp-verify-" + Guid.NewGuid().ToString("N")); - [Fact] [Slow] public void VerifyRecordsTheProvenBoundInEachPartFile() { - string folder = Folder(); - Directory.CreateDirectory(folder); - try - { - File.Copy(RepositoryFiles.PartFile("parts", "delay-2.json"), Path.Combine(folder, "delay-2.json")); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-verify").Made(); + string folder = temp.Path; + File.Copy(RepositoryFiles.PartFile("parts", "delay-2.json"), Path.Combine(folder, "delay-2.json")); - int exit = CommandLine.Run(new[] { "verify", "--library", folder, "--seconds", "10" }); + int exit = CommandLine.Run(new[] { "verify", "--library", folder, "--seconds", "10" }); - Assert.Equal(0, exit); - ProvenBound? proven = PartLibraryFiles.Load(folder).Parts.Single().Part!.Proven; - Assert.True(proven?.AllInputs); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Equal((int)ExitCode.Success, exit); + ProvenBound? proven = PartLibraryFiles.Load(folder).Parts.Single().Part!.Proven; + Assert.True(proven?.AllInputs); } [Fact] [Slow] public void OnlyVerifiesThePartsWhoseContractNamesMatch() { - string folder = Folder(); - Directory.CreateDirectory(folder); - try + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-verify").Made(); + string folder = temp.Path; + foreach (string file in new[] { "delay-2.json", "sequencer-2.json" }) { - foreach (string file in new[] { "delay-2.json", "sequencer-2.json" }) - { - LibraryPart unproven = RepositoryFiles.ReadPart("parts", file) with { Proven = null }; - File.WriteAllText(Path.Combine(folder, file), PartLibraryFiles.ToJson(unproven)); - } + LibraryPart unproven = RepositoryFiles.ReadPart("parts", file) with { Proven = null }; + File.WriteAllText(Path.Combine(folder, file), PartLibraryFiles.ToJson(unproven)); + } - int exit = CommandLine.Run(new[] { "verify", "--library", folder, "--only", "DELAY", "--seconds", "10" }); + int exit = CommandLine.Run(new[] { "verify", "--library", folder, "--only", "DELAY", "--seconds", "10" }); - Assert.Equal(0, exit); - Dictionary proven = PartLibraryFiles.Load(folder).Parts.Select(module => module.Part!).ToDictionary(part => part.Contract.Name, part => part.Proven); - Assert.NotNull(proven["delay 2"]); - Assert.Null(proven["sequencer 2"]); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Equal((int)ExitCode.Success, exit); + Dictionary proven = PartLibraryFiles.Load(folder).Parts.Select(module => module.Part!).ToDictionary(part => part.Contract.Name, part => part.Proven); + Assert.NotNull(proven["delay 2"]); + Assert.Null(proven["sequencer 2"]); } [Fact] public void VerifyExitsWithTwoAndRecordsTheCounterexample() { - string folder = Folder(); - Directory.CreateDirectory(folder); - string file = Path.Combine(folder, "register.json"); - try - { - Part broken = BoundedCheckTests.RegisterFailingAtTwenty(); - File.WriteAllText(file, PartLibraryFiles.ToJson(Verifier.Measure(broken, new EvaluationBudget()).ToLibraryPart(broken, new PartOrigin(0, "by hand", 0)))); + using var console = new ConsoleCapture(); + using var temp = new TempFolder("snp-verify").Made(); + string file = Path.Combine(temp.Path, "register.json"); + Part broken = PartFixtures.RegisterFailingAtTwenty(); + File.WriteAllText(file, PartLibraryFiles.ToJson(PartFixtures.Measured(broken, PartFixtures.ByHand))); - int exit = CommandLine.Run(new[] { "verify", "--part", file, "--bound", "24" }); + int exit = CommandLine.Run(new[] { "verify", "--part", file, "--bound", "24" }); - Assert.Equal((int)ExitCode.Refuted, exit); - Assert.Contains("\"FailsAt\": \"n=20\"", File.ReadAllText(file)); - } - finally - { - Directory.Delete(folder, recursive: true); - } + Assert.Equal((int)ExitCode.Refuted, exit); + Assert.Contains("\"FailsAt\": \"n=20\"", File.ReadAllText(file)); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Compilation/CompilerTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Compilation/CompilerTests.cs deleted file mode 100644 index 2ce816a..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Compilation/CompilerTests.cs +++ /dev/null @@ -1,213 +0,0 @@ -using SnpEvolution.Compilation; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; -using SnpEvolution.Simulation; - -namespace SnpEvolution.Tests.Compilation -{ - public class CompilerTests - { - private static readonly int[] Fibonacci = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584 }; - - private static FitnessEvaluator SequenceEvaluator(IReadOnlyList values) => - new FitnessEvaluator(new SequentialCpuEngine(), new SequenceTask("target", values), new SimulationOptions(50, 2, OutputTiming.Interval), 1, new Random(1), new EvaluationBudget()); - - private static IReadOnlyList Generated(Network network, int maxSteps = 400) => - new ExhaustiveCpuEngine().CollectOutputs(new[] { network }, new SimulationOptions(maxSteps, 20, OutputTiming.Interval), new Random(1))[0]; - - [Fact] - public void FitsFibonacciAsTheSumOfTheTwoValuesBefore() - { - Recurrence? recurrence = Recurrence.Fit(Fibonacci.Take(16).ToList()); - - Assert.NotNull(recurrence); - Assert.Equal(new[] { 1, 1 }, recurrence!.Coefficients); - Assert.Equal(new[] { 1, 1 }, recurrence.Initial); - } - - [Fact] - public void FindsNoRecurrenceForASequenceWithoutOne() - { - Assert.Null(Recurrence.Fit(new[] { 1, 2, 1, 3, 1, 4, 1, 5 })); - } - - // The network is fitted to 16 values and keeps going: the next two are right as well. - [Fact] - public void CompiledFibonacciMakesExactlyTheGapsAndContinuesPastTheTarget() - { - Recurrence recurrence = Recurrence.Fit(Fibonacci.Take(16).ToList())!; - Network network = RecurrenceCompiler.Compile(recurrence); - - FitnessResult result = SequenceEvaluator(Fibonacci).Evaluate(network); - - Assert.Equal(1f, result.Fitness); - Assert.Equal(15, network.Neurons.Count); - Assert.All(network.Neurons, neuron => Assert.True(neuron.Rules.Count <= 3)); - } - - [Theory] - [InlineData(new[] { 1, 2, 4, 8, 16, 32, 64 })] - [InlineData(new[] { 1, 2, 5, 12, 29, 70, 169 })] - [InlineData(new[] { 1, 1, 2, 4, 7, 13, 24, 44, 81 })] - [InlineData(new[] { 3, 3, 3, 3, 3, 3 })] - [InlineData(new[] { 2, 1, 3, 4, 7, 11, 18, 29 })] - [InlineData(new[] { 5, 1, 1, 2, 3, 5, 8, 13 })] - public void CompiledRecurrencesMakeTheirSequences(int[] values) - { - Recurrence recurrence = Recurrence.Fit(values)!; - - Assert.Equal(1f, SequenceEvaluator(values).Evaluate(RecurrenceCompiler.Compile(recurrence)).Fitness); - } - - [Fact] - public void ProgramsReadAndWriteTheSameWay() - { - RegisterProgram program = RegisterProgram.Parse("0: ADD r1 -> 1 | 2\n1: SUB r1 -> 2 else 3\n2: ADD r0 -> 3\n3: HALT\n"); - - Assert.Equal(program.ToString(), RegisterProgram.Parse(program.ToString()).ToString()); - Assert.Equal(2, program.RegisterCount); - } - - [Fact] - public void ProgramsGenerateEveryNumberAlongEveryChoice() - { - RegisterProgram evens = RegisterProgram.Parse("ADD r0 -> 1\nADD r0 -> 0 | 2\nHALT"); - - (IReadOnlyList outputs, bool complete, _) = evens.Generate(maxSteps: 20); - - Assert.Equal(new[] { 2, 4, 6, 8, 10, 12 }, outputs.Take(6)); - Assert.False(complete); - } - - [Fact] - public void TheOutputRegisterCannotBeSubtractedFrom() - { - Assert.NotNull(RegisterProgram.Parse("SUB r0 -> 1 else 1\nHALT").Problem()); - } - - [Theory] - [InlineData("ADD r0 -> 1\nADD r0 -> 2\nADD r0 -> 3\nHALT", new[] { 3 })] - [InlineData("ADD r0 -> 1\nADD r0 -> 2 | 3\nADD r0 -> 3\nHALT", new[] { 2, 3 })] - // Counts 2 or 3 into r1, then moves it into r0 one at a time. - [InlineData("ADD r1 -> 1\nADD r1 -> 2 | 3\nADD r1 -> 3\nSUB r1 -> 4 else 5\nADD r0 -> 3\nHALT", new[] { 2, 3 })] - // Two registers subtracted from by different instructions, with a choice inside the loop. - [InlineData("ADD r1 -> 1\nADD r2 -> 2 | 3\nADD r2 -> 3\nSUB r2 -> 4 else 5\nADD r0 -> 3\nSUB r1 -> 6 else 7\nADD r0 -> 5\nHALT", new[] { 2, 3 })] - public void CompiledProgramsGenerateWhatTheProgramDoes(string text, int[] expected) - { - RegisterProgram program = RegisterProgram.Parse(text); - Assert.Equal(expected, program.Generate().Outputs); - - Assert.Equal(expected, Generated(RegisterMachineCompiler.Compile(program))); - } - - // Outputs a, then (a, b) becomes (b, a + b): every Fibonacci number, with r4 only there to make the choice. - private const string FibonacciProgram = @" - 0: ADD r1 -> 1 - 1: ADD r2 -> 2 - 2: ADD r4 -> 3 | 5 - 3: SUB r1 -> 4 else 12 - 4: ADD r0 -> 3 - 5: SUB r1 -> 6 else 7 - 6: ADD r3 -> 5 - 7: SUB r2 -> 8 else 10 - 8: ADD r1 -> 9 - 9: ADD r3 -> 7 - 10: SUB r3 -> 11 else 2 - 11: ADD r2 -> 10 - 12: HALT"; - - [Fact] - public void ACompiledFibonacciProgramGeneratesTheSameNumbersAsTheProgram() - { - RegisterProgram program = RegisterProgram.Parse(FibonacciProgram); - (IReadOnlyList outputs, _, int steps) = program.Generate(outputLimit: 21, valueLimit: 100); - - IReadOnlyList generated = Generated(RegisterMachineCompiler.Compile(program), 4 * (steps + 2) + 31); - - Assert.Equal(new[] { 1, 2, 3, 5, 8, 13, 21 }, outputs); - Assert.Equal(outputs, generated.Where(number => number <= 21)); - } - - [Fact] - public void ProgramSearchFindsASmallSetAndItsNetworkGeneratesIt() - { - var search = new ProgramSearch(new ProgramSearchSettings(Population: 60)); - var target = new GeneratorTask("{2,3}", new[] { 2, 3 }, new JaccardFitness(new[] { 2, 3 })); - var budget = new EvaluationBudget(); - - SearchOutcome best = search.Run(new SearchRequest(target, budget, new Random(1), _ => { }) { MaxGenerations = 400 }); - - Assert.True(best.Solved, best.Best!.ToString()); - Assert.Equal(new[] { 2, 3 }, Generated(RegisterMachineCompiler.Compile(best.Best!))); - Assert.True(budget[EvaluationKind.InterpreterRun] > 0); - } - - [Fact] - public void ProgramSearchLearnsLargeSetsInStagesThatEndPastTheLargestNumber() - { - Assert.Equal(new long[] { 5, 13, 34, 89, 233, 610, 1976 }, ProgramScoring.StageBounds(new[] { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 })); - Assert.Equal(new long[] { 14 }, ProgramScoring.StageBounds(new[] { 2, 4, 6 })); - } - - [Fact] - public void AStageChecksEveryTargetNumberUpToItsBoundAndNothingExtra() - { - var scoring = new ProgramScoring(new[] { 1, 2, 3, 5, 8 }, 2_000, new EvaluationBudget()); - - ScoredProgram scored = scoring.Score(RegisterProgram.Parse("ADD r0 -> 1\nADD r0 -> 0 | 2\nHALT")); - - Assert.Equal(5, scored.Checks.Count); - } - - [Fact] - public void AnEditNeverLeavesAProgramWithoutInstructions() - { - var edits = new ProgramEdits(16, 4, new Random(1)); - RegisterProgram single = RegisterProgram.Parse("HALT"); - - Assert.All(Enumerable.Range(0, 300), _ => Assert.NotEmpty(edits.Mutate(single).Instructions)); - } - - // Only the first numbers are generated before the limit on register 0 cuts the computations off, and that - // still counts as complete, since nothing at or below the limit is lost. - [Fact] - public void TheOutputLimitLosesNothingBelowIt() - { - RegisterProgram evens = RegisterProgram.Parse("ADD r0 -> 1\nADD r0 -> 0 | 2\nHALT"); - - (IReadOnlyList outputs, bool complete, _) = evens.Generate(outputLimit: 7); - - Assert.Equal(new[] { 2, 4, 6 }, outputs); - Assert.True(complete); - } - - [Fact] - [Slow] - public void ShrinkingKeepsTheNetworkCorrectAndNeverGrowsIt() - { - int[] values = { 1, 2, 4, 8, 16, 32 }; - Network compiled = RecurrenceCompiler.Compile(Recurrence.Fit(values)!); - FitnessEvaluator evaluator = SequenceEvaluator(values); - var random = new Random(5); - var space = new GenomeSpace(RuleForm: RuleForm.Standard, MaxNeurons: compiled.Neurons.Count, MaxInitialSpikes: 8, MaxProduce: 2); - var factory = new NetworkFactory(space, new ExpressionGenerator(ExpressionGenerator.SimpleTemplates, 4, random), random); - var setup = new NetworkSetup(20, 0.5f, factory, () => compiled, new NetworkScoring(() => new SequentialCpuEngine(), new SimulationOptions(50, 2, OutputTiming.Interval), 1)); - - SearchOutcome shrink = SearchCatalog.Shrink.Run(new SearchRequest(evaluator.Task, new EvaluationBudget(), random, _ => { }) - { - Seeds = new[] { new Individual(compiled) }, - MaxGenerations = 30, - Networks = setup, - }); - - Assert.Equal(30, shrink.Generations); - Assert.True(HardwareCost.Of(shrink.Best!.Genes).CompareTo(HardwareCost.Of(compiled)) <= 0); - Assert.True(evaluator.ConfirmSolved(shrink.Best!.Genes).Solved); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Compilation/RecurrenceCompilerTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RecurrenceCompilerTests.cs new file mode 100644 index 0000000..06f0d33 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RecurrenceCompilerTests.cs @@ -0,0 +1,56 @@ +using SnpEvolution.Compilation; +using SnpEvolution.Model; +using SnpEvolution.Search.Fitness; +using static SnpEvolution.Tests.Fixtures.Runs; + +namespace SnpEvolution.Tests.Compilation +{ + public class RecurrenceCompilerTests + { + private const int FittedValues = 16; + + [Fact] + public void FitsFibonacciAsTheSumOfTheTwoValuesBefore() + { + Recurrence? recurrence = Recurrence.Fit(Sequences.Fibonacci.Take(FittedValues).ToList()); + + Assert.NotNull(recurrence); + Assert.Equal(new[] { 1, 1 }, recurrence!.Coefficients); + Assert.Equal(new[] { 1, 1 }, recurrence.Initial); + } + + [Fact] + public void FindsNoRecurrenceForASequenceWithoutOne() + { + Assert.Null(Recurrence.Fit(new[] { 1, 2, 1, 3, 1, 4, 1, 5 })); + } + + // The network is fitted to 16 values and keeps going: the next two are right as well. + [Fact] + public void CompiledFibonacciMakesExactlyTheGapsAndContinuesPastTheTarget() + { + Recurrence recurrence = Recurrence.Fit(Sequences.Fibonacci.Take(FittedValues).ToList())!; + Network network = RecurrenceCompiler.Compile(recurrence); + + FitnessResult result = SequenceEvaluator(Sequences.Fibonacci).Evaluate(network); + + Assert.Equal(1f, result.Fitness); + Assert.Equal(15, network.Neurons.Count); + Assert.All(network.Neurons, neuron => Assert.True(neuron.Rules.Count <= 3)); + } + + [Theory] + [InlineData(new[] { 1, 2, 4, 8, 16, 32, 64 })] + [InlineData(new[] { 1, 2, 5, 12, 29, 70, 169 })] + [InlineData(new[] { 1, 1, 2, 4, 7, 13, 24, 44, 81 })] + [InlineData(new[] { 3, 3, 3, 3, 3, 3 })] + [InlineData(new[] { 2, 1, 3, 4, 7, 11, 18, 29 })] + [InlineData(new[] { 5, 1, 1, 2, 3, 5, 8, 13 })] + public void CompiledRecurrencesMakeTheirSequences(int[] values) + { + Recurrence recurrence = Recurrence.Fit(values)!; + + Assert.Equal(1f, SequenceEvaluator(values).Evaluate(RecurrenceCompiler.Compile(recurrence)).Fitness); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterMachineCompilerTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterMachineCompilerTests.cs new file mode 100644 index 0000000..4c56f7f --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterMachineCompilerTests.cs @@ -0,0 +1,53 @@ +using SnpEvolution.Compilation; +using static SnpEvolution.Tests.Fixtures.Runs; + +namespace SnpEvolution.Tests.Compilation +{ + public class RegisterMachineCompilerTests + { + // Outputs a, then (a, b) becomes (b, a + b): every Fibonacci number, with r4 only there to make the choice. + private const string FibonacciProgram = @" + 0: ADD r1 -> 1 + 1: ADD r2 -> 2 + 2: ADD r4 -> 3 | 5 + 3: SUB r1 -> 4 else 12 + 4: ADD r0 -> 3 + 5: SUB r1 -> 6 else 7 + 6: ADD r3 -> 5 + 7: SUB r2 -> 8 else 10 + 8: ADD r1 -> 9 + 9: ADD r3 -> 7 + 10: SUB r3 -> 11 else 2 + 11: ADD r2 -> 10 + 12: HALT"; + + [Theory] + [InlineData("ADD r0 -> 1\nADD r0 -> 2\nADD r0 -> 3\nHALT", new[] { 3 })] + [InlineData("ADD r0 -> 1\nADD r0 -> 2 | 3\nADD r0 -> 3\nHALT", new[] { 2, 3 })] + // Counts 2 or 3 into r1, then moves it into r0 one at a time. + [InlineData("ADD r1 -> 1\nADD r1 -> 2 | 3\nADD r1 -> 3\nSUB r1 -> 4 else 5\nADD r0 -> 3\nHALT", new[] { 2, 3 })] + // Two registers subtracted from by different instructions, with a choice inside the loop. + [InlineData("ADD r1 -> 1\nADD r2 -> 2 | 3\nADD r2 -> 3\nSUB r2 -> 4 else 5\nADD r0 -> 3\nSUB r1 -> 6 else 7\nADD r0 -> 5\nHALT", new[] { 2, 3 })] + public void CompiledProgramsGenerateWhatTheProgramDoes(string text, int[] expected) + { + RegisterProgram program = RegisterProgram.Parse(text); + Assert.Equal(expected, program.Generate().Outputs); + + Assert.Equal(expected, Generated(RegisterMachineCompiler.Compile(program))); + } + + [Fact] + public void ACompiledFibonacciProgramGeneratesTheSameNumbersAsTheProgram() + { + RegisterProgram program = RegisterProgram.Parse(FibonacciProgram); + (IReadOnlyList outputs, _, int steps) = program.Generate(outputLimit: 21, valueLimit: 100); + + // A generous bound on the network steps the compiled program needs to output 21 and count down its halt. + int networkSteps = 4 * (steps + 2) + 31; + IReadOnlyList generated = Generated(RegisterMachineCompiler.Compile(program), networkSteps); + + Assert.Equal(new[] { 1, 2, 3, 5, 8, 13, 21 }, outputs); + Assert.Equal(outputs, generated.Where(number => number <= 21)); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterProgramTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterProgramTests.cs new file mode 100644 index 0000000..b8bcd9d --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Compilation/RegisterProgramTests.cs @@ -0,0 +1,48 @@ +using SnpEvolution.Compilation; + +namespace SnpEvolution.Tests.Compilation +{ + public class RegisterProgramTests + { + private const string EvensProgram = "ADD r0 -> 1\nADD r0 -> 0 | 2\nHALT"; + + [Fact] + public void ProgramsReadAndWriteTheSameWay() + { + RegisterProgram program = RegisterProgram.Parse("0: ADD r1 -> 1 | 2\n1: SUB r1 -> 2 else 3\n2: ADD r0 -> 3\n3: HALT\n"); + + Assert.Equal(program.ToString(), RegisterProgram.Parse(program.ToString()).ToString()); + Assert.Equal(2, program.RegisterCount); + } + + [Fact] + public void ProgramsGenerateEveryNumberAlongEveryChoice() + { + RegisterProgram evens = RegisterProgram.Parse(EvensProgram); + + (IReadOnlyList outputs, bool complete, _) = evens.Generate(maxSteps: 20); + + Assert.Equal(new[] { 2, 4, 6, 8, 10, 12 }, outputs.Take(6)); + Assert.False(complete); + } + + [Fact] + public void TheOutputRegisterCannotBeSubtractedFrom() + { + Assert.NotNull(RegisterProgram.Parse("SUB r0 -> 1 else 1\nHALT").Problem()); + } + + // Only the first numbers are generated before the limit on register 0 cuts the computations off, and that + // still counts as complete, since nothing at or below the limit is lost. + [Fact] + public void TheOutputLimitLosesNothingBelowIt() + { + RegisterProgram evens = RegisterProgram.Parse(EvensProgram); + + (IReadOnlyList outputs, bool complete, _) = evens.Generate(outputLimit: 7); + + Assert.Equal(new[] { 2, 4, 6 }, outputs); + Assert.True(complete); + } + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/AlgorithmTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/AlgorithmTests.cs deleted file mode 100644 index 6a42a6c..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/AlgorithmTests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Networks; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class AlgorithmTests - { - private static readonly BenchmarkSettings Settings = BenchmarkSettings.Default with { Seeds = 1 }; - - private static readonly BenchmarkTask Identity = TaskSuite.Functions.First(task => task.Name == "Compute n"); - - private sealed class DelegateEvaluator : IPopulationEvaluator - { - private readonly Func fitness; - - public DelegateEvaluator(Func fitness) => this.fitness = fitness; - - public int Evaluated { get; private set; } - - public IReadOnlyList EvaluateAll(IReadOnlyList networks) - { - Evaluated += networks.Count; - return networks.Select(network => new FitnessResult(fitness(network), Array.Empty())).ToList(); - } - } - - private static NetworkFactory Factory(Random random) => - new NetworkFactory(new GenomeSpace(RuleForm: RuleForm.Standard), new ExpressionGenerator(ExpressionGenerator.SimpleTemplates, 4, random), random); - - // Rewards networks for having exactly three neurons, which every algorithm should manage. - private static float ThreeNeurons(Network network) => 1f / (1 + Math.Abs(network.Neurons.Count - 3)); - - public static TheoryData StructuralAlgorithms => new TheoryData( - SearchCatalog.Evolution.Where(search => !search.EvolvesRulesOnly).Select(choice => choice.Name)); - - [Theory] - [Slow] - [MemberData(nameof(StructuralAlgorithms))] - public void StructuralAlgorithmsSolveTheIdentityFunction(string name) - { - EvolutionSearch algorithm = SearchCatalog.Evolution.Single(search => search.Name == name); - - RunOutcome outcome = Benchmark.RunOnce(algorithm, Identity, seed: 1, budget: 4000, Settings); - - Assert.True(outcome.Solved, $"{name} reached {outcome.BestFitness}"); - Assert.True(outcome.Best!.Exact); - } - - [Theory] - [MemberData(nameof(StructuralAlgorithms))] - public void BestNeverGetsWorseAndPopulationStaysFilled(string name) - { - var random = new Random(3); - NetworkFactory factory = Factory(random); - var evaluator = new DelegateEvaluator(ThreeNeurons); - IGeneticAlgorithm algorithm = SearchCatalog.Evolution.Single(search => search.Name == name) - .Create(new EvolutionContext(12, 0.5f, random, factory.NewNetwork, evaluator, factory, _ => { })); - - float best = float.MinValue; - for (int generation = 0; generation < 15; generation++) - { - algorithm.NextGeneration(); - Assert.True(algorithm.Best!.Fitness >= best); - best = algorithm.Best.Fitness; - Assert.NotEmpty(algorithm.Population); - } - - Assert.Equal(1f, best); - Assert.Equal(16, algorithm.Generation); - } - - [Fact] - public void RankingPrefersTheSmallerOfEquallyFitNetworks() - { - var small = new Individual(Identity()); - var large = new Individual(ReferenceNetworks.NaturalNumbers()); - small.Record(new FitnessResult(0.5f, Array.Empty())); - large.Record(new FitnessResult(0.5f, Array.Empty())); - - Assert.Same(small, Ranking.Rank(new[] { large, small })[0]); - - large.Record(new FitnessResult(0.6f, Array.Empty())); - Assert.Same(large, Ranking.Rank(new[] { small, large })[0]); - } - - [Fact] - public void MuPlusLambdaKeepsMuParents() - { - var random = new Random(0); - NetworkFactory factory = Factory(random); - var strategy = new MuPlusLambdaStrategy(3, 9, random, factory.NewNetwork, new DelegateEvaluator(ThreeNeurons), WeightedMutation.Structural(1, factory)); - - strategy.NextGeneration(); - strategy.NextGeneration(); - - Assert.Equal(3, strategy.Population.Count); - } - - [Fact] - public void MapElitesKeepsOneEliteForEachSize() - { - var random = new Random(0); - NetworkFactory factory = Factory(random); - var elites = new MapElites(10, random, factory.NewNetwork, new DelegateEvaluator(ThreeNeurons), new NeuronCrossover(), WeightedMutation.Structural(1, factory)); - - for (int generation = 0; generation < 10; generation++) - { - elites.NextGeneration(); - } - - Assert.Equal(elites.Population.Count, elites.Population.Select(elite => MapElites.Cell(elite.Genes)).Distinct().Count()); - Assert.True(elites.Population.Count > 3); - } - - [Fact] - public void SpeciesDistanceIsZeroOnlyForTheSameStructure() - { - Assert.Equal(0, SpeciatedAlgorithm.Distance(Identity(), Identity())); - Assert.True(SpeciatedAlgorithm.Distance(Identity(), ReferenceNetworks.NaturalNumbers()) > 2); - Assert.Equal(SpeciatedAlgorithm.Distance(Identity(), AlwaysOutputsOne()), SpeciatedAlgorithm.Distance(AlwaysOutputsOne(), Identity())); - } - - [Fact] - public void BenchmarkSummarisesRunsPerAlgorithmAndTask() - { - var best = new Individual(Identity()); - RunOutcome[] outcomes = - { - new RunOutcome("A", "T", 1, true, 100, 1, best), - new RunOutcome("A", "T", 2, true, 300, 1, best), - new RunOutcome("A", "T", 3, false, 1000, 0.5f, null), - }; - - BenchmarkRow row = Assert.Single(Benchmark.Summarise(outcomes)); - - Assert.Equal((3, 2, 200.0), (row.Runs, row.Solved, row.MedianEvaluationsToSolve!.Value)); - Assert.Equal(2.5 / 3, row.MeanBestFitness, precision: 5); - Assert.Equal(Identity().Size, row.MeanSolvedSize); - Assert.Contains("2/3", Benchmark.FormatTable(new[] { row })); - Assert.StartsWith("task,algorithm", Benchmark.FormatCsv(new[] { row })); - } - - [Fact] - [Slow] - public void SelectorHalvesTheCandidatesUntilOneIsLeft() - { - List candidates = SearchCatalog.Evolution.Take(3).ToList(); - - SelectionResult result = AlgorithmSelector.Select(candidates, TaskSuite.Generators[0], Settings, initialBudget: 60); - - Assert.Contains(result.Winner, candidates); - Assert.Equal(new[] { 3, 2 }, result.Rounds.Select(round => round.Standings.Count)); - Assert.Equal(new[] { 60L, 120L }, result.Rounds.Select(round => round.Budget)); - Assert.Equal(result.Rounds[0].Standings.Take(2).Select(row => row.Algorithm), result.Rounds[0].Advancing); - Assert.NotNull(result.BestFound); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/CheckScoringTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/CheckScoringTests.cs deleted file mode 100644 index 2a67332..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/CheckScoringTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Simulation; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class CheckScoringTests - { - [Fact] - public void SequenceChecksScoreEachGapInPlaceEvenAfterAMistake() - { - var task = new SequenceTask("test", new[] { 2, 1, 2 }); - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), task, new SimulationOptions(20, 2, OutputTiming.Interval), 1, new Random(1), new EvaluationBudget()); - - FitnessResult result = evaluator.Evaluate(PingPong()); - - Assert.Equal(new[] { 1f, 0f, 1f }, result.Checks); - Assert.Equal("gap 2 (1)", task.CheckName(1)); - Assert.Equal(new[] { 2, 1, 2 }, ((SequenceTask)task.Focus(1)!).Expected); - } - - [Fact] - public void LexicasePicksSpecialistsButNeverANetworkThatIsBestAtNothing() - { - var random = new Random(1); - List ranked = Ranking.Rank(new[] - { - Scored(PingPong(), 0.6f, 0.6f, 0.6f), - Scored(Identity(), 0.5f, 1f, 0f), - Scored(NeverOutputs(), 0.5f, 0f, 1f), - Scored(Chain(), 0.4f, 0.5f, 0.5f), - }); - Func pick = new LexicaseSelection().Prepare(ranked, random); - - List picks = Enumerable.Range(0, 200).Select(_ => pick()).ToList(); - - Assert.Contains(picks, individual => individual.Fitness == 0.5f && individual.Checks[1] == 1f); - Assert.Contains(picks, individual => individual.Fitness == 0.5f && individual.Checks[0] == 1f); - Assert.DoesNotContain(picks, individual => individual.Fitness == 0.4f); - } - - [Fact] - public void DiagnosisFindsTheFirstCheckNoNetworkDoes() - { - CheckDiagnosis diagnosis = CheckDiagnosis.Of(new[] { Scored(PingPong(), 0.5f, 1, 1, 0, 0), Scored(Identity(), 0.4f, 1, 0, 0.5f, 1) }); - - Assert.Equal(new[] { 2 }, diagnosis.Unsolved); - Assert.Equal(2, diagnosis.Frontier); - Assert.StartsWith("No network yet does gap 3 (5)", diagnosis.Describe(new SequenceTask("fib", new[] { 1, 2, 5, 8 }))); - } - - [Fact] - public void ATriggeredPartCountsItsGapsFromTheTrigger() - { - var task = (TriggeredSequenceTask)new SequenceTask("test", new[] { 1, 2, 2, 2, 7 }).Triggered(1)!; - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), task, new SimulationOptions(30, 2, OutputTiming.Interval), 1, new Random(1), new EvaluationBudget()); - - FitnessResult result = evaluator.Evaluate(TriggeredTwos()); - Cut cut = ModuleCuts.Whole(TriggeredTwos()); - - Assert.Equal(new[] { 2, 2, 2 }, task.Expected); - Assert.Equal(1, task.InputCount); - Assert.Equal(new[] { 1f, 1f, 1f }, result.Checks); - Assert.True(Solved.Solves(result.Fitness)); - Assert.Equal(3, cut.Body.Neurons.Count); - Assert.Equal(new[] { 0 }, cut.Inputs); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/CompositionTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/CompositionTests.cs deleted file mode 100644 index a1ada19..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/CompositionTests.cs +++ /dev/null @@ -1,247 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; -using SnpEvolution.Simulation; -using SnpEvolution.Storage; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; - -namespace SnpEvolution.Tests.Evolution -{ - public class CompositionTests - { - private const int MaxGlue = 7; - - // Verified parts of every port kind the first parts use, one with its output role on a port. - private static ModuleLibrary Library() - { - var library = new ModuleLibrary(); - Part increment = ReferenceParts.Increment(); - library.AddPart(Verified(increment with { Network = increment.Network.WithNeuron(2, increment.Network.Neurons[2].WithRoles(true, false)) }), "a test"); - library.AddPart(Verified(ReferenceParts.Register()), "a test"); - library.AddPart(Verified(ReferenceParts.Add()), "a test"); - library.AddPart(Verified(ReferenceParts.Delay(2)), "a test"); - return library; - } - - private static NetworkFactory Factory(int inputCount, Random random) => - new NetworkFactory(new GenomeSpace(InputCount: inputCount, RuleForm: RuleForm.Standard, MaxNeurons: MaxGlue), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - - private static string Json(Network network) => NetworkFiles.ToJson(network); - - [Fact] - [Slow] - public void RandomCompositionsSurviveFlatteningAndRecovery() - { - ModuleLibrary library = Library(); - for (int seed = 0; seed < 200; seed++) - { - var random = new Random(seed); - Composition composition = RandomComposition.Of(library, Factory(seed % 3, random), 1 + seed % 4, random); - - Network flat = composition.Flatten(library); - - Assert.InRange(composition.Glue.Count, 1, seed % 3 + 3); - - Assert.Equal(composition, Composition.Recover(flat, library)); - Assert.Equal(Json(flat), Json(Composition.Recover(flat, library)!.Flatten(library))); - } - } - - [Fact] - [Slow] - public void CompositionsStillRoundTripAfterAnyEditTheSearchMakes() - { - ModuleLibrary library = Library(); - for (int seed = 0; seed < 50; seed++) - { - var random = new Random(seed); - var space = new CompositionSpace(library, Factory(seed % 2, random), new CompositionMix(), random); - WeightedMutation mutation = space.Mutation(1); - Network network = space.NewNetwork(); - for (int step = 0; step < 20; step++) - { - network = mutation.Mutate(network, random); - - Composition composition = Composition.Recover(network, library)!; - Assert.Equal(composition, Composition.Recover(composition.Flatten(library), library)); - Assert.Equal(Json(network), Json(composition.Flatten(library))); - Assert.True(composition.ThroughPorts(library)); - Assert.InRange(composition.Glue.Count, 1, MaxGlue); - } - } - } - - [Fact] - public void TheTwoIncrementChainIsACompositionThatFlattensToTheSameNetwork() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - var first = new Network(increment.Part!.Part.Network.Neurons.Select(neuron => neuron.WithModule(new ModuleTag(increment.Id, 1))).ToList()); - Network chained = ModuleEdits.Insert(first, increment, 2, 24, library, new Random(1)); - PartCopy copy = PartWiring.Copies(chained, library).Single(each => each.Tag.Instance == 1); - - var composition = new Composition( - new[] { new PartInstance(1, increment.Id, 0), new PartInstance(2, increment.Id, 0) }, - Array.Empty(), - new[] { new PortWire(1, "out", 2, "n"), new PortWire(1, "done", 2, "start") }, - Array.Empty(), - new[] { new Endpoint(1, copy["start"]), new Endpoint(1, copy["n"]) }, - Array.Empty()); - - Assert.Equal(Json(chained), Json(composition.Flatten(library))); - Assert.Equal(composition, Composition.Recover(chained, library)); - } - - [Fact] - public void FlatteningTagsEveryPartNeuronWithItsInstance() - { - ModuleLibrary library = Library(); - Composition composition = RandomComposition.Of(library, Factory(1, new Random(3)), 3, new Random(3)); - - Network flat = composition.Flatten(library); - - Assert.Equal(composition.Glue.Count, flat.Neurons.Count(neuron => neuron.Module == null)); - Assert.Equal( - composition.Parts.Select(part => new ModuleTag(part.Module, part.Instance)), - flat.Neurons.Where(neuron => neuron.Module != null).Select(neuron => neuron.Module!).Distinct()); - } - - [Fact] - public void ACopyWhoseInsideChangedIsNotRecovered() - { - ModuleLibrary library = Library(); - Network flat = RandomComposition.Of(library, Factory(0, new Random(2)), 2, new Random(2)).Flatten(library); - int partNeuron = flat.Neurons.ToList().FindIndex(neuron => neuron.Module != null); - - Assert.Null(Composition.Recover(flat.WithNeuron(partNeuron, flat.Neurons[partNeuron].WithInitialSpikes(flat.Neurons[partNeuron].InitialSpikes + 1)), library)); - Assert.Null(Composition.Recover(flat.WithNeuron(partNeuron, flat.Neurons[partNeuron].WithRules(new[] { Rule.Standard("aaa", 3) })), library)); - } - - [Fact] - public void ALinkIntoAPartsOutPortDoesNotPassThroughItsPorts() - { - ModuleLibrary library = Library(); - Composition composition = RandomComposition.Of(library, Factory(0, new Random(4)), 1, new Random(4)); - PartInstance part = composition.Parts[0]; - PartPort outPort = Composition.PartOf(part, library).Part.Ports().First(port => port.Port.Direction == PortDirection.Out); - Composition bypassing = composition with { Links = composition.Links.Append(new Link(Endpoint.GlueAt(1), new Endpoint(part.Instance, outPort.Position))).ToList() }; - - Assert.True(composition.ThroughPorts(library)); - Assert.False(bypassing.ThroughPorts(library)); - Assert.Equal(composition, bypassing.OnlyThroughPorts(library)); - } - - [Fact] - public void TheSearchAdmitsNoMoreGlueThanItsCap() - { - ModuleLibrary library = Library(); - var random = new Random(6); - Composition composition = RandomComposition.Of(library, Factory(0, random), 1, random); - var space = new CompositionSpace(library, Factory(0, random), new CompositionMix(MaxGlue: composition.Glue.Count), random); - Composition overGlued = composition with { Glue = composition.Glue.Append(composition.Glue[0]).ToList() }; - - Assert.NotNull(space.Admit(composition.Flatten(library), dropStrayLinks: false)); - Assert.Null(space.Admit(overGlued.Flatten(library), dropStrayLinks: false)); - } - - // Glue may stop reading a part's port, but a glue edit that starts reading one would change how the part is used. - [Fact] - public void AGlueEditMayNotAddASynapseFromAPartToGlue() - { - ModuleLibrary library = Library(); - var random = new Random(8); - var space = new CompositionSpace(library, Factory(0, random), new CompositionMix(), random); - Composition composition = RandomComposition.Of(library, Factory(0, random), 1, random); - PartInstance part = composition.Parts[0]; - PartPort outPort = Composition.PartOf(part, library).Part.Ports().First(port => port.Port.Direction == PortDirection.Out); - var sender = new Endpoint(part.Instance, outPort.Position); - int unread = Enumerable.Range(1, composition.Glue.Count).First(glue => !composition.Links.Contains(new Link(sender, Endpoint.GlueAt(glue)))); - Network network = composition.Flatten(library); - Network reading = (composition with { Links = composition.Links.Append(new Link(sender, Endpoint.GlueAt(unread))).ToList() }).Flatten(library); - - Assert.Same(network, new CompositionEdit(space, new FixedEdit(reading), glueOnly: true).Mutate(network, random)); - } - - private sealed class FixedEdit : IMutation - { - private readonly Network result; - - public FixedEdit(Network result) => this.result = result; - - public Network Mutate(Network network, Random random) => result; - } - - [Fact] - public void RemovingAPartDropsItsWiresAndKeepsTheRest() - { - ModuleLibrary library = Library(); - for (int seed = 0; seed < 20; seed++) - { - var random = new Random(seed); - Network network = RandomComposition.Of(library, Factory(0, random), 3, random).Flatten(library); - Composition before = Composition.Recover(network, library)!; - - Composition after = Composition.Recover(new RemovePart(library).Mutate(network, random), library)!; - - Assert.True(after.Parts.Count >= before.Parts.Count - 1); - Assert.All(after.Parts, part => Assert.Contains(part, before.Parts)); - Assert.All(after.Wires, wire => Assert.Contains(wire, before.Wires)); - Assert.Equal(before.Glue.Count, after.Glue.Count); - } - } - - [Fact] - [Slow] - public void ABenchmarkRunOfCompositionSearchBuildsFromTheGivenParts() - { - ModuleLibrary library = Library(); - BenchmarkSettings settings = BenchmarkSettings.Default with { Seeds = 1, Parts = library.Parts.Select(module => module.Part!).ToList() }; - EvolutionSearch algorithm = SearchCatalog.CompositionMapElites; - - RunOutcome outcome = Benchmark.RunOnce(algorithm, TaskSuite.Functions.First(task => task.Name == "Compute n + 1"), seed: 1, budget: 400, settings); - - Assert.Contains(outcome.Best!.Genes.Neurons, neuron => neuron.Module != null); - } - - // Every network a composition run keeps is exactly glue and library parts: no part neuron is ever changed. - [Theory] - [Slow] - [InlineData(true)] - [InlineData(false)] - public void ARunInCompositionModeNeverChangesAPartNeuron(bool mapElites) - { - ModuleLibrary library = Library(); - var random = new Random(5); - NetworkFactory factory = Factory(1, random); - var task = FunctionTask.Of("n + 2", n => n + 2, new[] { 1, 2, 3 }); - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), task, new SimulationOptions(30, 2, OutputTiming.Interval), 1, random, new EvaluationBudget()); - IGeneticAlgorithm run = (mapElites ? SearchCatalog.CompositionMapElites : SearchCatalog.CompositionTournament) - .Create(new EvolutionContext(16, 0.5f, random, factory.NewNetwork, evaluator, factory, _ => { }, Parts: library)); - - bool sawParts = false; - for (int generation = 0; generation < 30; generation++) - { - run.NextGeneration(); - sawParts |= run.Population.Any(individual => individual.Genes.Neurons.Any(neuron => neuron.Module != null)); - Assert.All(run.Population, individual => - { - Composition? composition = Composition.Recover(individual.Genes, library); - Assert.NotNull(composition); - Assert.True(composition!.ThroughPorts(library)); - Assert.InRange(composition.Glue.Count, 1, MaxGlue); - }); - } - Assert.True(sawParts); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/EvaluationBudgetTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/EvaluationBudgetTests.cs deleted file mode 100644 index 18235c6..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/EvaluationBudgetTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Simulation; - -namespace SnpEvolution.Tests.Evolution -{ - public class EvaluationBudgetTests - { - // An evaluator reuses the same case objects for every network, so trials sharing the first trial's input are one per network. - private sealed class CountingEngine : ISimulationEngine - { - private readonly ISimulationEngine inner; - private long networks; - - public CountingEngine(ISimulationEngine inner) => this.inner = inner; - - public long Networks => Interlocked.Read(ref networks); - - public EngineSupport Support => inner.Support; - - public IReadOnlyList Run(IReadOnlyList trials, SimulationOptions options, Random random) - { - Interlocked.Add(ref networks, trials.Count(trial => ReferenceEquals(trial.Input, trials[0].Input))); - return inner.Run(trials, options, random); - } - } - - [Fact] - public void APhaseChargesItsParentAndStopsAtItsOwnLimit() - { - var run = new EvaluationBudget(limit: 100); - EvaluationBudget phase = run.Phase(10, EvaluationSource.Proposals); - - phase.Charge(EvaluationKind.Network, 10, EvaluationSource.Main); - phase.Charge(EvaluationKind.ExhaustiveCheck, 3); - - Assert.True(phase.IsSpent); - Assert.False(run.IsSpent); - Assert.Equal(10, run[EvaluationSource.Proposals]); - Assert.Equal(0, run[EvaluationSource.Main]); - Assert.Equal(3, run[EvaluationKind.ExhaustiveCheck]); - } - - [Fact] - public void OnlyNetworkEvaluationsCountTowardsTheLimit() - { - var budget = new EvaluationBudget(limit: 5); - - budget.Charge(EvaluationKind.ExhaustiveCheck, 50); - budget.Charge(EvaluationKind.ProofStep, 50); - budget.Charge(EvaluationKind.InterpreterRun, 50); - budget.Charge(EvaluationKind.JitterRun, 50); - Assert.False(budget.IsSpent); - - budget.Charge(EvaluationKind.Network, 5); - Assert.True(budget.IsSpent); - } - - [Fact] - public void WithoutALimitNothingIsSpent() - { - var budget = new EvaluationBudget(); - budget.Charge(EvaluationKind.Network, long.MaxValue / 2); - - Assert.False(budget.IsSpent); - Assert.True(new EvaluationBudget().Phase(0).IsSpent); - } - - [Fact] - public void TheReportNamesOtherKindsOnlyWhenSomeWereSpent() - { - var budget = new EvaluationBudget(); - budget.Charge(EvaluationKind.Network, 2_500); - Assert.Equal("Evaluations: 2,500 main run, 0 side runs, 0 incubation, 0 verification, 0 proposed parts; 2,500 in all.", budget.Report().Describe()); - - budget.Charge(EvaluationKind.ExhaustiveCheck, 7); - Assert.EndsWith(" Besides them, 7 exhaustive checks.", budget.Report().Describe()); - } - - [Fact] - public void AVerifierChargesOneExhaustiveCheckPerNetworkItRuns() - { - var budget = new EvaluationBudget(); - - Verifier.Measure(ReferenceParts.Delay(2), budget); - - Assert.Equal(1, budget[EvaluationKind.ExhaustiveCheck]); - Assert.Equal(0, budget.Networks); - } - - [Fact] - [Slow] - public void EveryNetworkAModularRunScoresIsCharged() - { - var engine = new CountingEngine(new SequentialCpuEngine()); - var settings = new Settings - { - Target = new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34 }), - Task = Catalog.TargetTask, - Engine = new CatalogEntry("counting", _ => engine), - Repetitions = 2, - PopulationSize = 10, - MaxGenerations = 60, - IterativeEvolution = false, - StagnationPatience = 10, - Modules = true, - }; - var budget = new EvaluationBudget(); - - EvolutionSession.Evolve(settings, settings.SelectedTask, factory => factory.NewNetwork(), new Random(3), _ => { }, budget); - - Assert.True(budget[EvaluationSource.SideRun] > 0); - Assert.Equal(engine.Networks, budget.Networks); - } - - [Fact] - [Slow] - public void EveryNetworkABenchmarkRunScoresIsChargedRetestsIncluded() - { - var engine = new CountingEngine(new ExhaustiveCpuEngine()); - BenchmarkSettings settings = BenchmarkSettings.Default with { Seeds = 1, CreateEngine = () => engine }; - - RunOutcome outcome = Benchmark.RunOnce(Catalog.StructuralDefault, TaskSuite.Functions.First(task => task.Name == "Compute n"), seed: 1, budget: 4000, settings); - - Assert.True(outcome.Solved); - Assert.Equal(engine.Networks, outcome.Evaluations); - } - - [Fact] - [Slow] - public void EveryNetworkAPartSearchScoresIsChargedAndItsChecksBesideThem() - { - var engine = new CountingEngine(new ExhaustiveCpuEngine()); - var settings = new PartSearchSettings(5_000, 1_000, 30, Catalog.StructuralDefault, () => engine); - - PartOutcome outcome = PartSearch.Evolve(PartFixtures.DelayContract(2), 1, settings, new EvaluationBudget(), _ => { }); - - Assert.True(outcome.Solved); - Assert.Equal(engine.Networks, outcome.Spent.Networks); - Assert.True(outcome.Spent[EvaluationKind.ExhaustiveCheck] > 0); - Assert.Equal(outcome.Spent.Networks + outcome.Spent[EvaluationKind.ExhaustiveCheck], outcome.Evaluations); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/HardwareProfileTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/HardwareProfileTests.cs deleted file mode 100644 index 5fff3f5..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/HardwareProfileTests.cs +++ /dev/null @@ -1,176 +0,0 @@ -using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Networks; -using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class HardwareProfileTests - { - private static readonly GenomeSpace ProfileSpace = new GenomeSpace(InputCount: 2, RuleForm: RuleForm.Mixed, MaxNeurons: 8, MaxDelay: 3, DuplicateNeurons: true, HardwareProfile: true); - - // A fitness and checks that vary with size, so selection and lexicase have something to choose by. - private static RecordingEvaluator Evaluator() => new RecordingEvaluator(network => 1f / (1 + network.Size % 7), network => new[] { network.Size % 2f }); - - private static NetworkFactory Factory(int seed) - { - var random = new Random(seed); - return new NetworkFactory(ProfileSpace, new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - } - - [Fact] - public void NetworksMadeUnderTheProfileFitItWithoutBeingConformed() - { - NetworkFactory factory = Factory(1); - - for (int network = 0; network < 300; network++) - { - Network made = factory.NewNetwork(); - Assert.Empty(HardwareProfile.Problems(made)); - } - } - - [Fact] - public void NoNetworkMadeOrMutatedByAnyAlgorithmUnderTheProfileBreaksIt() - { - foreach (EvolutionSearch choice in SearchCatalog.Evolution.Where(search => search is not CompositionSearch)) - { - NetworkFactory factory = Factory(choice.Name.Length); - var evaluator = Evaluator(); - IGeneticAlgorithm algorithm = choice.Create(new EvolutionContext(20, 1f, factory.Random, factory.NewNetwork, evaluator, factory, _ => { }, Lexicase: true)); - - for (int generation = 0; generation < 15; generation++) - { - algorithm.NextGeneration(); - } - - Assert.True(evaluator.Seen.Count > 100, choice.Name); - Network? broken = evaluator.Seen.FirstOrDefault(network => !HardwareProfile.Fits(network)); - Assert.True(broken == null, $"{choice.Name} made a network outside the profile: {string.Join(" ", broken == null ? new string[0] : HardwareProfile.Problems(broken))}"); - } - } - - [Fact] - public void EveryStructuralEditIsPutBackWithinTheProfile() - { - NetworkFactory factory = Factory(7); - var context = new EvolutionContext(10, 1f, factory.Random, factory.NewNetwork, Evaluator(), factory, _ => { }); - foreach (var edit in context.StructuralMutation(1).Edits) - { - var conformed = context.Conformed(edit.Edit); - Network network = factory.NewNetwork(); - for (int step = 0; step < 200; step++) - { - network = conformed.Mutate(network, factory.Random); - Assert.True(HardwareProfile.Fits(network), $"{edit.Name}: {string.Join(" ", HardwareProfile.Problems(network))}"); - } - } - } - - [Fact] - public void PartSearchUnderTheProfileFindsAndShrinksAProfilePart() - { - var settings = new PartSearchSettings(5_000, 1_000, 30, Catalog.StructuralDefault, () => new ExhaustiveCpuEngine(), HardwareProfile: true); - - PartOutcome outcome = PartSearch.Evolve(PartFixtures.DelayContract(2), 1, settings, new EvaluationBudget(), _ => { }); - - Assert.True(outcome.Solved); - Assert.Empty(HardwareProfile.Problems(outcome.Part!.Network)); - } - - [Fact] - public void ShrinkingUnderTheProfileNeverLeavesIt() - { - var random = new Random(4); - var factory = new NetworkFactory(new GenomeSpace(InputCount: 1, MaxDelay: 3, HardwareProfile: true), new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - (var crossover, var edits) = ShrinkSearch.Operators(factory); - Network network = factory.NewNetwork(); - - for (int step = 0; step < 300; step++) - { - network = edits.Mutate(crossover.Cross(network, factory.NewNetwork(), random), random); - Assert.Empty(HardwareProfile.Problems(network)); - } - } - - [Fact] - public void RejectsAParityConditionAndNamesTheRule() - { - var network = new Network(new[] - { - Neuron(0, new[] { 2 }, HardwareProfile.ThresholdRule(2)), - OutputNeuron(0, new Rule("a(aa)*", 0, true)), - }); - - string problem = Assert.Single(HardwareProfile.Problems(network)); - - Assert.StartsWith("Neuron 2, rule 1 (a(aa)* -> a):", problem); - Assert.Contains("accepts 1, 3, 5, 7, 9, ...", problem); - } - - [Fact] - public void NamesEveryOtherWayOutOfTheProfile() - { - var network = new Network(new[] - { - Neuron(2, new[] { 2 }, Standard("a+", 1, produce: 2), new Rule("aa+", 2, true)), - }); - - IReadOnlyList problems = HardwareProfile.Problems(network); - - Assert.Contains(problems, problem => problem.Contains("starts with 2 spike(s)")); - Assert.Contains(problems, problem => problem.Contains("has 2 rules")); - Assert.Contains(problems, problem => problem.Contains("rule 1 (a+/a -> aa): it consumes 1 spike(s)")); - Assert.Contains(problems, problem => problem.Contains("rule 1 (a+/a -> aa): it sends 2 spikes")); - Assert.Contains(problems, problem => problem.Contains("rule 2 (aa+ -> a;2): its delay of 2 holds the neuron")); - } - - [Theory] - [InlineData("a+", 1)] - [InlineData("aa+", 2)] - [InlineData("a{3,}", 3)] - [InlineData("aaa*", 2)] - [InlineData("(aa|aaa)a*", 2)] - [InlineData("a*", 1)] - [InlineData("a(aa)*", null)] - [InlineData("aaa", null)] - [InlineData("b", null)] - public void ReadsTheThresholdOfAThresholdCondition(string expression, int? threshold) - { - Assert.Equal(threshold, HardwareProfile.Threshold(SpikeCondition.Parse(expression))); - } - - [Fact] - public void ConformsToTheNearestThresholdRule() - { - Rule conformed = HardwareProfile.Conform(Standard("a(aa)+", 2, delay: 2)); - Assert.Equal(("a{3,}", 2, true, true, false), (conformed.Expression, conformed.Delay, conformed.Fire, conformed.Axonal, conformed.IsStandard)); - - Rule forgetting = HardwareProfile.Conform(new Rule("aa", 1, false)); - Assert.Equal(("a{2,}", 0, false, false), (forgetting.Expression, forgetting.Delay, forgetting.Fire, forgetting.Axonal)); - - Neuron neuron = HardwareProfile.Conform(Neuron(3, new[] { 2 }, new Rule("aa+", 0, true), new Rule("a", 0, true))); - Assert.Equal(0, neuron.InitialSpikes); - Assert.Single(neuron.Rules); - } - - [Fact] - public void LeavesAFittingNetworkAsItIs() - { - var network = new Network(new[] { Neuron(0, new[] { 2 }, HardwareProfile.ThresholdRule(1, delay: 2)), OutputNeuron(0, HardwareProfile.ThresholdRule(3, fire: false)) }); - - Assert.Same(network, HardwareProfile.Conform(network)); - } - - [Fact] - public void IsOffUnlessAskedFor() - { - Assert.False(new GenomeSpace().HardwareProfile); - Assert.False(new PartSearchSettings(1, 1, 1, SearchCatalog.Evolution[0], () => null!).HardwareProfile); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModularEvolutionTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModularEvolutionTests.cs deleted file mode 100644 index c2e7a1f..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModularEvolutionTests.cs +++ /dev/null @@ -1,144 +0,0 @@ -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class ModularEvolutionTests - { - // A fixed population that records what it is given, so the modular loop can be watched without evolving. - internal sealed class StubAlgorithm : IGeneticAlgorithm - { - public List Individuals { get; } = new List(); - - public List Immigrants { get; } = new List(); - - public int Rescores { get; private set; } - - public IReadOnlyList Population => Individuals; - - public int Generation { get; private set; } = 1; - - public Individual? Best => Individuals.Count == 0 ? null : Ranking.Rank(Individuals)[0]; - - public IReadOnlyList> FitnessHistory => Array.Empty>(); - - public void NextGeneration() => Generation++; - - public void Immigrate(IReadOnlyList newcomers) => Immigrants.AddRange(newcomers); - - public void Rescore() => Rescores++; - } - - [Fact] - public void AStallBuildsAModuleOnTheSideAndOffersItToTheBestNetworks() - { - var library = new ModuleLibrary(); - var task = new SequenceTask("test", new[] { 2, 2, 5, 2 }); - var main = new StubAlgorithm(); - main.Individuals.AddRange(new[] { Scored(PingPong(), 0.5f, 1, 1, 0, 0), Scored(Chain(), 0.4f, 1, 0, 0, 0) }); - var focusTasks = new List(); - var modular = new ModularEvolution(main, library, null, () => task, (focus, _) => - { - focusTasks.Add(focus); - var side = new StubAlgorithm(); - side.Individuals.Add(Scored(AlwaysOutputsOne(), 1)); - return side; - }, new ModulePolicy(Patience: 2, SideGenerations: 5, CompositeFraction: 0.5, IncubationGenerations: 0), 4, 10, new Random(1), _ => { }); - - for (int generation = 0; generation < 3; generation++) - { - modular.NextGeneration(); - } - - Assert.Equal(1, modular.SideRuns); - Assert.Equal(new[] { 2, 5, 2 }, ((SequenceTask)focusTasks.Single()).Expected); - Assert.Single(library.Modules); - Assert.Equal(2, main.Immigrants.Count); - Assert.All(main.Immigrants, network => Assert.Contains(network.Neurons, neuron => neuron.Module?.Module == library.Modules[0].Id)); - } - - [Fact] - public void EveryOtherSideRunBuildsAPartThatWaitsForATrigger() - { - var task = new SequenceTask("test", new[] { 2, 2, 5, 2 }); - var main = new StubAlgorithm(); - main.Individuals.AddRange(new[] { Scored(PingPong(), 0.5f, 1, 1, 0, 0), Scored(Chain(), 0.4f, 1, 0, 0, 0) }); - var parts = new List(); - var modular = new ModularEvolution(main, new ModuleLibrary(), null, () => task, (part, _) => - { - parts.Add(part); - var side = new StubAlgorithm(); - side.Individuals.Add(Scored(AlwaysOutputsOne(), 1)); - return side; - }, new ModulePolicy(Patience: 2, SideGenerations: 5, CompositeFraction: 0.5, IncubationGenerations: 0), 4, 10, new Random(1), _ => { }); - - for (int generation = 0; generation < 5; generation++) - { - modular.NextGeneration(); - } - - Assert.Equal(2, parts.Count); - Assert.IsType(parts[0]); - Assert.IsType(parts[1]); - } - - [Fact] - public void NetworksGivenAModuleIncubateAndASolvedPartIsNotEvolvedAgain() - { - var library = new ModuleLibrary(); - var task = new SequenceTask("test", new[] { 2, 2, 5, 2 }); - var main = new StubAlgorithm(); - main.Individuals.AddRange(new[] { Scored(PingPong(), 0.5f, 1, 1, 0, 0), Scored(Chain(), 0.4f, 1, 0, 0, 0) }); - var parts = new List(); - var incubated = new List>(); - var modular = new ModularEvolution(main, library, null, () => task, (sideTask, seeds) => - { - var side = new StubAlgorithm(); - if (seeds == null) - { - parts.Add(sideTask); - side.Individuals.Add(Scored(AlwaysOutputsOne(), 1)); - } - else - { - incubated.Add(seeds); - side.Individuals.AddRange(seeds.Select(seed => Scored(seed, 0.6f))); - } - return side; - }, new ModulePolicy(Patience: 2, SideGenerations: 5, CompositeFraction: 0.5, Triggered: false, IncubationGenerations: 4), 4, 10, new Random(1), _ => { }); - - for (int generation = 0; generation < 5; generation++) - { - modular.NextGeneration(); - } - - Module module = library.Modules.Single(); - Assert.Single(parts); - Assert.Equal(2, incubated.Count); - Assert.All(incubated, seeds => Assert.All(seeds, network => Assert.Contains(network.Neurons, neuron => neuron.Module?.Module == module.Id))); - Assert.Equal(incubated.SelectMany(seeds => seeds), main.Immigrants); - Assert.Equal((2, 2), (module.Uses, module.Wins)); - } - - [Fact] - public void ASolvedStageIsKeptAsAModule() - { - var library = new ModuleLibrary(); - var main = new StubAlgorithm(); - main.Individuals.Add(Scored(PingPong(), 1)); - var modular = new ModularEvolution(main, library, null, () => new SequenceTask("twos", new[] { 2, 2 }), (_, _) => new StubAlgorithm(), - new ModulePolicy(), 4, 10, new Random(1), _ => { }); - - modular.Rescore(); - - Assert.Equal(1, main.Rescores); - Assert.Contains("solved twos", library.Modules.Single().Origin); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleEditTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleEditTests.cs deleted file mode 100644 index f0e6fae..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleEditTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; -using SnpEvolution.Storage; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class ModuleEditTests - { - [Fact] - public void InsertingAModuleWiresItsPortsAndTagsItsNeurons() - { - var library = new ModuleLibrary(); - Module module = library.Add(ModuleCuts.Cut(Chain(), new[] { 1, 2 }), "a test")!; - - for (int seed = 0; seed < 20; seed++) - { - Network inserted = ModuleEdits.Insert(PingPong(), module, 7, 10, library, new Random(seed)); - - Assert.Equal(4, inserted.Neurons.Count); - Assert.Single(inserted.Neurons, neuron => neuron.IsOutput); - Assert.All(inserted.Neurons.Skip(2), neuron => Assert.Equal(new ModuleTag(module.Id, 7), neuron.Module)); - Assert.Contains(inserted.Neurons.Take(2), neuron => neuron.Connections.Contains(3)); - Assert.Equal(new[] { 4 }, inserted.Neurons[2].Connections); - } - Network full = PingPong(); - Assert.Same(full, ModuleEdits.Insert(full, module, 8, 3, library, new Random(1))); - } - - [Fact] - public void FrozenModulesRejectEditsToTheirInsidesButNotToTheirSpikes() - { - var library = new ModuleLibrary(); - Module module = ModuleOf(library, Chain()); - Network network = ModuleEdits.Insert(PingPong(), module, 1, 10, library, new Random(1)); - int last = network.Neurons.Count - 1; - - Network rewired = network.WithRule(last, 0, network.Neurons[last].Rules[0].WithDelay(2)); - Network refilled = network.WithNeuron(last, network.Neurons[last].WithInitialSpikes(5)); - Network hostChanged = network.WithRule(0, 0, network.Neurons[0].Rules[0].WithDelay(2)); - - Assert.False(ModuleEdits.KeepsModules(network, rewired)); - Assert.True(ModuleEdits.KeepsModules(network, refilled)); - Assert.True(ModuleEdits.KeepsModules(network, hostChanged)); - Assert.Same(network, new ProtectModules(new DelegateMutation(_ => rewired)).Mutate(network, new Random(1))); - Assert.All(new DissolveModule().Mutate(network, new Random(1)).Neurons, neuron => Assert.Null(neuron.Module)); - } - - private sealed class DelegateMutation : IMutation - { - private readonly Func edit; - - public DelegateMutation(Func edit) => this.edit = edit; - - public Network Mutate(Network network, Random random) => edit(network); - } - - [Fact] - public void AModuleThatTakesTheOutputIsFedByTheOldOne() - { - var library = new ModuleLibrary(); - Module module = ModuleOf(library, TriggeredTwos()); - int takeovers = 0; - - for (int seed = 0; seed < 20; seed++) - { - Network inserted = ModuleEdits.Insert(PingPong(), module, 1, 10, library, new Random(seed)); - int output = inserted.Neurons.ToList().FindIndex(neuron => neuron.IsOutput); - if (output >= PingPong().Neurons.Count) - { - takeovers++; - int oldOutput = PingPong().Neurons.ToList().FindIndex(neuron => neuron.IsOutput); - Assert.Contains(output + 1, inserted.Neurons[oldOutput].Connections); - } - } - Assert.InRange(takeovers, 1, 19); - } - - [Fact] - public void ModuleTagsSurviveSavingAndAreShownInTheNotation() - { - var library = new ModuleLibrary(); - Network network = ModuleEdits.Insert(PingPong(), ModuleOf(library, Chain()), 3, 10, library, new Random(1)); - - Network loaded = NetworkFiles.FromJson(NetworkFiles.ToJson(network))!; - - Assert.Equal(network.Neurons.Select(neuron => neuron.Module), loaded.Neurons.Select(neuron => neuron.Module)); - Assert.Contains("[module 1]", NetworkNotation.Format(network)); - Assert.DoesNotContain("Module", NetworkFiles.ToJson(PingPong())); - } - - [Fact] - public void EditsWithModulesKeepNetworksWellFormedAndModulesWhole() - { - for (int seed = 0; seed < 20; seed++) - { - var random = new Random(seed); - var factory = new NetworkFactory(new GenomeSpace(InputCount: seed % 2, RuleForm: RuleForm.Standard, MaxNeurons: 12), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - var library = new ModuleLibrary(); - library.Add(ModuleCuts.Whole(Chain()), "a test"); - library.Add(ModuleCuts.Cut(Chain(), new[] { 1 }), "a test"); - Part increment = seed % 2 == 0 ? ReferenceParts.Increment() : PartFixtures.PaddedIncrement(); - library.AddPart(Verifier.Measure(increment, new EvaluationBudget()).ToLibraryPart(increment, new PartOrigin(1, "a test", 0)), "a test"); - WeightedMutation mutation = WeightedMutation.Structural(1, factory, modules: new ModuleSupport(library)); - Network network = factory.NewNetwork(); - for (int step = 0; step < 150; step++) - { - WeightedEdit edit = mutation.Edits[random.Next(mutation.Edits.Count)]; - Network next = edit.Edit.Mutate(network, random); - Assert.True(edit.Edit is DissolveModule or SwapPart || ModuleEdits.KeepsModules(network, next), edit.Name); - network = next; - Assert.Single(network.Neurons, neuron => neuron.IsOutput); - Assert.InRange(network.Neurons.Count, 1, factory.Space.MaxNeurons); - for (int position = 1; position <= network.Neurons.Count; position++) - { - Assert.DoesNotContain(position, network.Neurons[position - 1].Connections); - Assert.All(network.Neurons[position - 1].Connections, target => Assert.InRange(target, 1, network.Neurons.Count)); - } - } - } - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleFixtures.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleFixtures.cs deleted file mode 100644 index f18db61..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ModuleFixtures.cs +++ /dev/null @@ -1,46 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - internal static class ModuleFixtures - { - internal static Individual Scored(Network network, float fitness, params float[] checks) - { - var individual = new Individual(network); - individual.Record(new FitnessResult(fitness, Array.Empty(), Checks: checks)); - return individual; - } - - // n1 -> n2 -> n3 (out), with n2 waiting a step. - internal static Network Chain() => new Network(new[] - { - Neuron(1, new[] { 2 }, Standard("a", 1)), - Neuron(0, new[] { 3 }, Standard("a", 1, delay: 1)), - OutputNeuron(0, Standard("a", 1)), - }); - - // A part checked against its contract on the exhaustive engine and recorded as a library part would be. - internal static LibraryPart Verified(Part part) - { - PartMeasurement measurement = Verifier.Measure(part, new EvaluationBudget()); - Assert.True(measurement.Verdict is Verdict.Passed, measurement.Description); - return measurement.ToLibraryPart(part, new PartOrigin(1, "a test", 0)); - } - - internal static Module ModuleOf(ModuleLibrary library, Network network) => library.Add(ModuleCuts.Whole(network), "a test")!; - - // The input passes the trigger on, n2 waits a step, and the output and n4 then pass a spike back and forth. - internal static Network TriggeredTwos() => new Network(new[] - { - InputNeuron(new[] { 2 }, Standard("a", 1)), - Neuron(0, new[] { 3 }, Standard("a", 1)), - new Neuron(new[] { Standard("a", 1) }, 0, new[] { 4 }, true), - Neuron(0, new[] { 3 }, Standard("a", 1)), - }); - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/MultiplicationTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/MultiplicationTests.cs deleted file mode 100644 index 4df3704..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/MultiplicationTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Simulation; -using Xunit.Abstractions; - -namespace SnpEvolution.Tests.Evolution -{ - // The epic's done-when: n1 x n2 solved by composition search that reuses the add loop promoted from hand-built parts. - public class MultiplicationTests - { - private readonly ITestOutputHelper output; - - public MultiplicationTests(ITestOutputHelper output) => this.output = output; - - private static BenchmarkSettings Settings(ModuleLibrary library) => BenchmarkSettings.Default with - { - Seeds = 1, - PopulationSize = 40, - Repetitions = 2, - Lexicase = true, - CreateEngine = () => new SequentialCpuEngine(), - Parts = library.Parts.Select(module => module.Part!).ToList(), - }; - - private static BenchmarkTask Multiply => TaskSuite.Contracts.Single(task => task.Name == "Contract multiply"); - - private static EvolutionSearch MapElites => SearchCatalog.CompositionMapElites; - - [Fact] - [Slow] - public void CompositionSearchSolvesMultiplicationByReusingThePromotedAddLoop() - { - RunOutcome outcome = Benchmark.RunOnce(MapElites, Multiply, seed: 3, budget: 3_000, Settings(HandBuiltMachines.Library())); - - output.WriteLine($"Solved in {outcome.Evaluations} evaluations; parts in best: {string.Join(", ", outcome.Reuse!.Select(count => $"{count.Contract} {count.Direct} ({count.Nested})"))}"); - Assert.True(outcome.Solved); - Assert.True(outcome.Promoted); - Assert.Equal(1, outcome.Reuse!.Single(count => count.Contract == "add loop").Direct); - Assert.Equal(2, outcome.Reuse!.Single(count => count.Contract == "register").Nested); - } - - [Fact] - public void TheBenchmarkTableReportsWhichPartsTheBestNetworksHold() - { - var outcomes = new[] - { - new RunOutcome("composition", "multiply", 1, false, 100, 0.9f, null, new[] { new PartCount("add loop", 1, 0), new PartCount("register", 0, 2) }), - new RunOutcome("composition", "multiply", 2, false, 300, 0.8f, null, new[] { new PartCount("add", 2, 0) }), - }; - - BenchmarkRow row = Benchmark.Summarise(outcomes).Single(); - string table = Benchmark.FormatTable(new[] { row }); - - Assert.Equal("add 1/2, add loop 1/2", row.ReuseText); - Assert.Equal(2, row.Reuse!.Single(use => use.Contract == "add").MeanCopies); - Assert.Contains("Parts in best (runs)", table); - Assert.Contains("add loop 1/2", Benchmark.FormatCsv(new[] { row })); - } - - [Fact] - public void AFlatRunReportsNoPartColumn() - { - BenchmarkRow row = Benchmark.Summarise(new[] { new RunOutcome("flat", "n + 1", 1, false, 100, 0.5f, null) }).Single(); - - Assert.Null(row.Reuse); - Assert.DoesNotContain("Parts in best", Benchmark.FormatTable(new[] { row })); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/NetworkGenerationTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/NetworkGenerationTests.cs deleted file mode 100644 index c775b62..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/NetworkGenerationTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Text.RegularExpressions; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Networks; - -namespace SnpEvolution.Tests.Evolution -{ - public class NetworkGenerationTests - { - [Fact] - public void ExpressionsFillBothPlaceholdersWithShortSpikeRuns() - { - var generator = new ExpressionGenerator(new[] { "x(y)+" }, maxSpikeGroupSize: 4, new Random(0)); - - for (int sample = 0; sample < 50; sample++) - { - Assert.Matches(new Regex(@"^a{1,3}\(a{1,3}\)\+$"), generator.Next()); - } - } - - [Fact] - public void EachPlaceholderGetsItsOwnSpikeRun() - { - var generator = new ExpressionGenerator(new[] { "x,y" }, maxSpikeGroupSize: 4, new Random(0)); - - bool anyDiffer = Enumerable.Range(0, 50).Select(_ => generator.Next().Split(',')).Any(parts => parts[0] != parts[1]); - - Assert.True(anyDiffer); - } - - [Fact] - public void RandomTopologyHasOneOutputAndValidConnections() - { - for (int seed = 0; seed < 100; seed++) - { - var random = new Random(seed); - Network network = RandomTopology.Create(new ExpressionGenerator(ExpressionGenerator.SimpleTemplates, 4, random), 4, random); - - Assert.Single(network.Neurons, neuron => neuron.IsOutput); - for (int position = 1; position <= network.Neurons.Count; position++) - { - IReadOnlyList connections = network.Neurons[position - 1].Connections; - Assert.NotEmpty(connections); - Assert.DoesNotContain(position, connections); - Assert.All(connections, target => Assert.InRange(target, 1, network.Neurons.Count)); - Assert.Equal(connections.Distinct().OrderBy(target => target), connections); - } - } - } - - [Fact] - public void RandomExpressionsKeepEachRulesDelayAndFiring() - { - Network reference = ReferenceNetworks.NaturalNumbers(); - - Network randomised = reference.WithRandomExpressions(() => "aaaa"); - - Assert.All(randomised.Neurons.SelectMany(neuron => neuron.Rules), rule => Assert.Equal("aaaa", rule.Expression)); - Assert.Equal( - reference.Neurons.SelectMany(neuron => neuron.Rules).Select(rule => (rule.Delay, rule.Fire)), - randomised.Neurons.SelectMany(neuron => neuron.Rules).Select(rule => (rule.Delay, rule.Fire))); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/OutputTargetTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/OutputTargetTests.cs deleted file mode 100644 index 52a194f..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/OutputTargetTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class OutputTargetTests - { - private static TrialResult Trains(params int[][] trains) => new TrialResult(Array.Empty(), false, TrialCoverage.Sampled, trains); - - private static FitnessResult Evaluate(ITask task, int maxSteps) => - new FitnessEvaluator(new ParallelCpuEngine(), task, new SimulationOptions(maxSteps, 5, OutputTiming.Interval), 3, new Random(0), new EvaluationBudget()).Evaluate(PingPong()); - - [Theory] - [InlineData("1,1,2,3,5,8,13", new[] { 1, 1, 2, 3, 5, 8, 13 })] - [InlineData("1, 1, 2, 3, 5 ...", new[] { 1, 1, 2, 3, 5 })] - [InlineData("{2 4 6}", new[] { 2, 4, 6 })] - [InlineData("[3,1,4]…", new[] { 3, 1, 4 })] - public void NumbersParseWithAnySeparatorsBracketsAndEllipsis(string input, int[] expected) - { - Assert.True(OutputTarget.TryParse(TargetKind.Sequence, input, out OutputTarget target)); - Assert.Equal(expected, target.Values); - } - - [Theory] - [InlineData("")] - [InlineData("1,0,2")] - [InlineData("1,-2")] - [InlineData("1,x")] - public void NumbersMustBePositiveIntegers(string input) - { - Assert.False(OutputTarget.TryParse(TargetKind.Set, input, out _)); - } - - [Fact] - public void BinaryWordsIgnoreSpacingAndNeedAOne() - { - Assert.True(OutputTarget.TryParse(TargetKind.BinaryWord, "0110 1001_0", out OutputTarget target)); - Assert.Equal(new[] { 0, 1, 1, 0, 1, 0, 0, 1, 0 }, target.Values); - Assert.False(OutputTarget.TryParse(TargetKind.BinaryWord, "0000", out _)); - Assert.False(OutputTarget.TryParse(TargetKind.BinaryWord, "0120", out _)); - } - - [Fact] - public void EachKindMakesItsTask() - { - var fitness = new JaccardFitness(new[] { 1 }); - - Assert.IsType(new OutputTarget(TargetKind.Set, new[] { 1, 1, 2 }).CreateTask(fitness)); - Assert.IsType(new OutputTarget(TargetKind.Sequence, new[] { 1, 1, 2 }).CreateTask(fitness)); - Assert.IsType(new OutputTarget(TargetKind.BinaryWord, new[] { 0, 1 }).CreateTask(fitness)); - } - - [Fact] - public void SequenceScoreRewardsTheRightGapsInOrder() - { - var task = new SequenceTask("fib", new[] { 1, 1, 2, 3 }); - - Assert.Equal(1f, task.Score(new[] { Trains(new[] { 4, 5, 6, 8, 11, 20 }) })); - Assert.Equal(0.5f, task.Score(new[] { Trains(new[] { 0, 1, 2 }) })); - // Half the runs right and half silent: the mean is a half and the worst run nothing, so a quarter. - Assert.Equal(0.25f, task.Score(new[] { Trains(new[] { 0, 1, 2, 4, 7 }, Array.Empty()) })); - } - - [Fact] - public void SequenceScoreStopsAtTheFirstWrongGap() - { - var task = new SequenceTask("fib", new[] { 1, 1, 2, 3 }); - - // Gaps 1,2,2,3: one right, then 2 for 1 earns a quarter, and the 2,3 after the mistake earn nothing. - Assert.Equal(0.3125f, task.Score(new[] { Trains(new[] { 0, 1, 3, 5, 8 }) }), precision: 5); - } - - [Fact] - public void SequenceNicheIsTheRightPrefixByTheLongestGap() - { - var task = new SequenceTask("fib", new[] { 1, 1, 2, 3, 5 }); - - Assert.Equal((3, 3), task.Niche(new[] { Trains(new[] { 0, 1, 2, 4, 9 }) })); - Assert.Equal((0, 0), task.Niche(new[] { Trains(new[] { 4 }) })); - } - - [Fact] - public void PrefixesKeepTheOpeningValues() - { - var sequence = new SequenceTask("fib", new[] { 1, 1, 2, 3, 5 }); - var word = new SpikeWordTask("w", new[] { true, false, true, true }); - - Assert.Equal(new[] { 1, 1, 2 }, ((SequenceTask)sequence.Prefix(3)).Expected); - Assert.Equal(new[] { true, false }, ((SpikeWordTask)word.Prefix(2)).Expected); - Assert.True(sequence.Prefix(3).StepsNeeded < sequence.StepsNeeded); - } - - [Fact] - public void SpikeWordNicheIsTheRightPrefixBySpikeCount() - { - var task = new SpikeWordTask("w", new[] { true, false, true, true }); - - // Fires on steps 0 and 2: right for three steps, with two spikes, as shares of 16 and 8 buckets. - Assert.Equal((12, 4), task.Niche(new[] { Trains(new[] { 0, 2 }) })); - } - - [Fact] - public void SequenceGivesPartialCreditForCloseGaps() - { - var task = new SequenceTask("twos", new[] { 2, 2 }); - - Assert.Equal(0.625f, task.Score(new[] { Trains(new[] { 0, 2, 5 }) }), precision: 5); - } - - [Fact] - public void BinaryWordScoreIsBalancedAccuracy() - { - var task = new SpikeWordTask("w", new[] { false, true, false, true }); - - Assert.Equal(1f, task.Score(new[] { Trains(new[] { 1, 3, 9 }) })); - Assert.Equal(0.5f, task.Score(new[] { Trains(Array.Empty()) })); - Assert.Equal(0.5f, task.Score(new[] { Trains(new[] { 0, 1, 2, 3 }) })); - } - - [Fact] - public void RunsAreLengthenedToFitTheWholeTarget() - { - FitnessResult result = Evaluate(new SequenceTask("twos", Enumerable.Repeat(2, 20).ToList()), maxSteps: 5); - - Assert.Equal(1f, result.Fitness); - Assert.StartsWith("intervals [2,2,2", result.Description); - } - - [Fact] - public void PingPongSpellsAlternatingBits() - { - FitnessResult result = Evaluate(new SpikeWordTask("01", Enumerable.Range(0, 12).Select(step => step % 2 == 1).ToList()), maxSteps: 3); - - Assert.Equal(1f, result.Fitness); - Assert.Equal("spikes 010101010101 / 010101010101", result.Description); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartLibraryTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartLibraryTests.cs deleted file mode 100644 index fb37d79..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartLibraryTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; - -namespace SnpEvolution.Tests.Evolution -{ - public class PartLibraryTests - { - private static readonly PartOrigin ByHand = new PartOrigin(0, "by hand", 0); - - private static LibraryPart Measured(Part part) => Verifier.Measure(part, new EvaluationBudget()).ToLibraryPart(part, ByHand); - - // The reference delay with a neuron that holds nothing and does nothing: the same behaviour at a higher cost. - private static Part PaddedDelay(int k) - { - Part delay = ReferenceParts.Delay(k); - return delay with { Network = new Network(delay.Network.Neurons.Append(new Neuron(new Rule[0], 0, new int[0], false)).ToList()) }; - } - - [Fact] - public void PartsThatReadTheSameOnEveryCaseHaveTheSameBehaviour() - { - Assert.Equal(Measured(ReferenceParts.Delay(2)).Behaviour, Measured(PaddedDelay(2)).Behaviour); - Assert.NotEqual(Measured(ReferenceParts.Delay(2)).Behaviour, Measured(PartFixtures.DelayFiringDoneTwice(2)).Behaviour); - } - - [Fact] - public void AddingALargerPartWithTheSameBehaviourChangesNothing() - { - var library = new ModuleLibrary(); - LibraryPart small = Measured(ReferenceParts.Delay(2)); - Module kept = library.AddPart(small, "first"); - - Module again = library.AddPart(Measured(PaddedDelay(2)), "second"); - - Assert.Same(kept, again); - Assert.Same(small, again.Part); - Assert.Equal("first", again.Origin); - Assert.Single(library.Parts); - } - - [Fact] - public void AddingASmallerPartReplacesItUnderTheSameId() - { - var log = new List(); - var library = new ModuleLibrary(log: log.Add); - Module kept = library.AddPart(Measured(PaddedDelay(2)), "first"); - LibraryPart small = Measured(ReferenceParts.Delay(2)); - - Module replaced = library.AddPart(small, "second"); - - Assert.Equal(kept.Id, replaced.Id); - Assert.Same(small, replaced.Part); - Assert.Equal(2, replaced.Body.Neurons.Count); - Assert.Same(replaced, library.Find(kept.Id)); - Assert.Single(library.Parts); - Assert.Contains(log, line => line.Contains($"Module {kept.Id}") && line.Contains("replaced")); - } - - [Fact] - public void PartsWithDifferentResultsOnACaseAreBothKept() - { - var library = new ModuleLibrary(); - - Module right = library.AddPart(Measured(ReferenceParts.Delay(2)), "first"); - Module firingTwice = library.AddPart(Measured(PartFixtures.DelayFiringDoneTwice(2)), "second"); - - Assert.NotEqual(right.Id, firingTwice.Id); - Assert.Equal(2, library.Parts.Count); - } - - [Fact] - public void PartsAreNeverDroppedToMakeRoomForHarvestedModules() - { - var library = new ModuleLibrary(capacity: 1); - library.AddPart(Measured(ReferenceParts.Delay(2)), "a part"); - - library.Add(ModuleCuts.Whole(ReferenceParts.Delay(3).Network), "harvested"); - library.Add(ModuleCuts.Whole(ReferenceParts.Delay(4).Network), "harvested"); - - Assert.Single(library.Parts); - Assert.Equal(2, library.Modules.Count); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartWiringTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartWiringTests.cs deleted file mode 100644 index 5e082c8..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PartWiringTests.cs +++ /dev/null @@ -1,274 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class PartWiringTests - { - private const int MaxNeurons = 24; - - // n + 2 on the same values the increment is checked on, with room for two increments in a row. - private static Contract PlusTwo() => new Contract( - "plus two", - Port.In("start", PortKind.Trigger), - new[] { Port.Out("done", PortKind.Trigger) }, - new[] { Port.In("n", PortKind.Count), Port.Out("out", PortKind.Count) }, - FirstParts.Values.Select(n => new ContractCase(new Dictionary { ["n"] = n }, new Dictionary { ["out"] = n + 2 }, "done")).ToList(), - Specifications.LatencyFor(2 * (FirstParts.Larger + 2))); - - // The first copy is the part's own network, so its in-ports are the network's inputs. - private static Network Chained(ModuleLibrary library, Module module, int seed) - { - var first = new Network(module.Part!.Part.Network.Neurons.Select(neuron => neuron.WithModule(new ModuleTag(module.Id, 1))).ToList()); - return ModuleEdits.Insert(first, module, 2, MaxNeurons, library, new Random(seed)); - } - - // The plus-two contract read from the out and done ports of the copy put in last. - private static PartMeasurement MeasurePlusTwo(Network network, ModuleLibrary library) - { - PartCopy last = PartWiring.Copies(network, library).OrderBy(copy => copy.Tag.Instance).Last(); - var binding = new PortBinding(new Dictionary { ["out"] = last["out"], ["done"] = last["done"] }); - return new Verifier(new ContractTask(PlusTwo(), binding), new EvaluationBudget()).Measure(network); - } - - private static NetworkFactory Factory(int maxNeurons, Random random) => - new NetworkFactory(new GenomeSpace(InputCount: 2, RuleForm: RuleForm.Standard, MaxNeurons: maxNeurons), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - - // Evolved parts carry the output role on a port, which the reference increment does not. - private static Part IncrementWithOutput() - { - Part increment = ReferenceParts.Increment(); - return increment with { Network = increment.Network.WithNeuron(2, increment.Network.Neurons[2].WithRoles(true, false)) }; - } - - private static string WireText(Wire wire) => $"{wire.From.Copy.Tag.Instance}.{wire.From.Port.Name}>{wire.To.Copy.Tag.Instance}.{wire.To.Port.Name}"; - - // The task's input feeds the part's in-port and its out-port feeds the task's, rather than any glue neuron at random. - [Fact] - public void AnInsertedPartIsWiredToTheTasksOwnPorts() - { - var library = new ModuleLibrary(); - library.AddPart(Verified(ReferenceParts.Register()), "a test"); - var task = new ContractTask(FirstParts.Named("register")); - var glue = new Network(Enumerable.Range(1, 4).Select(position => new Neuron(new[] { Rule.Standard("a", 1) }, 0, Array.Empty(), false, isInput: position <= 2)).ToList()); - var insert = new InsertModule(library, new GenomeSpace(InputCount: 2, RuleForm: RuleForm.Standard, MaxNeurons: MaxNeurons), task.Boundary); - - for (int seed = 0; seed < 10; seed++) - { - Network network = insert.Mutate(glue, new Random(seed)); - PartCopy copy = PartWiring.Copies(network, library).Single(); - - Assert.Contains(copy["n"], network.Neurons[1].Connections); - Assert.Contains(task.Binding["out"], network.Neurons[copy["out"] - 1].Connections); - } - } - - [Fact] - public void PartsShowTheirPortsAsNeuronPositions() - { - Part increment = ReferenceParts.Increment(); - - Assert.Equal(new[] { "start@1", "n@2", "out@3", "done@4" }, increment.Ports().Select(port => $"{port.Port.Name}@{port.Position}")); - } - - [Fact] - public void OnlyPortsOfTheSameKindAndWidthJoinOutToIn() - { - Port start = Port.In("start", PortKind.Trigger), done = Port.Out("done", PortKind.Trigger); - Port countIn = Port.In("n", PortKind.Count), countOut = Port.Out("out", PortKind.Count); - - Assert.True(PartWiring.Fits(done, start)); - Assert.True(PartWiring.Fits(countOut, countIn)); - Assert.False(PartWiring.Fits(start, done)); - Assert.False(PartWiring.Fits(countOut, start)); - Assert.False(PartWiring.Fits(Port.Out("out", PortKind.Interval), countIn)); - Assert.False(PartWiring.Fits(Port.Out("out", PortKind.Binary, 4), Port.In("n", PortKind.Binary, 5))); - Assert.True(PartWiring.Fits(Port.Out("out", PortKind.Binary, 4), Port.In("n", PortKind.Binary, 4))); - } - - [Fact] - public void TwoVerifiedIncrementsWiredByTypeAddTwoWithNoEvolution() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - - for (int seed = 0; seed < 5; seed++) - { - Network network = Chained(library, increment, seed); - - Assert.Equal(new[] { "1.out>2.n", "1.done>2.start" }, PartWiring.Wires(network, library).Select(WireText)); - PartMeasurement measurement = MeasurePlusTwo(network, library); - Assert.True(measurement.Verdict is Verdict.Passed, measurement.Description); - } - } - - [Fact] - public void AThirdCopyIsFedByAnEarlierOneButNeverDoublesUpAnInPort() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - - for (int seed = 0; seed < 10; seed++) - { - Network network = ModuleEdits.Insert(Chained(library, increment, seed), increment, 3, MaxNeurons, library, new Random(seed)); - - IReadOnlyList wires = PartWiring.Wires(network, library); - Assert.Equal(new[] { "n", "start" }, wires.Where(wire => wire.To.Copy.Tag.Instance == 3).Select(wire => wire.To.Port.Name).Order()); - Assert.All(wires.GroupBy(wire => wire.To.Position), group => Assert.Single(group)); - } - } - - [Fact] - public void APartInANetworkWithNoOtherPartIsWiredToUntypedNeurons() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - - Network network = ModuleEdits.Insert(PingPong(), increment, 1, MaxNeurons, library, new Random(1)); - - PartCopy copy = PartWiring.Copies(network, library).Single(); - Assert.Contains(network.Neurons.Take(2), neuron => neuron.Connections.Contains(copy["start"])); - Assert.Contains(network.Neurons.Take(2), neuron => neuron.Connections.Contains(copy["n"])); - Assert.Empty(PartWiring.Wires(network, library)); - } - - [Fact] - public void APartWhoseInsideWasEditedIsNoLongerTyped() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - Network network = Chained(library, increment, 1); - - Network edited = network.WithNeuron(4, network.Neurons[4].WithRules(new[] { Standard("a", 1) })); - - Assert.Single(PartWiring.Copies(edited, library)); - Assert.Empty(PartWiring.Wires(edited, library)); - } - - [Fact] - public void RewiringOnlyEverJoinsFittingPortsAndNeverFeedsAnInput() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - Network chained = Chained(library, increment, 1); - Network network = ModuleEdits.Insert(chained, increment, 3, MaxNeurons, library, new Random(1)); - var rewire = new RewirePort(library); - var seen = new HashSet(); - - for (int seed = 0; seed < 40; seed++) - { - Network rewired = rewire.Mutate(network, new Random(seed)); - - IReadOnlyList copies = PartWiring.Copies(rewired, library); - Assert.Equal(3, copies.Count); - Dictionary ports = copies.SelectMany(copy => copy.Ports).ToDictionary(port => port.Position); - foreach (CopyPort from in ports.Values) - { - foreach (int target in rewired.Neurons[from.Position - 1].Connections.Where(ports.ContainsKey).Where(target => ports[target].Copy != from.Copy)) - { - Assert.True(PartWiring.Fits(from.Port, ports[target].Port), $"{from.Port.Name} > {ports[target].Port.Name}"); - } - } - Assert.All(rewired.Neurons, neuron => Assert.DoesNotContain(neuron.Connections, target => rewired.Neurons[target - 1].IsInput)); - seen.Add(string.Join(" ", PartWiring.Wires(rewired, library).Select(WireText))); - } - Assert.True(seen.Count > 1); - } - - [Fact] - public void AGlueNeuronSitsOnAWireAndThePairStillAddsTwo() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - Network network = Chained(library, increment, 1); - var random = new Random(1); - - Network glued = new AddGlueNeuron(library, Factory(MaxNeurons, random)).Mutate(network, random); - - Assert.Equal(network.Neurons.Count + 1, glued.Neurons.Count); - Assert.Single(PartWiring.Wires(glued, library)); - Assert.Null(glued.Neurons[^1].Module); - PartMeasurement measurement = MeasurePlusTwo(glued, library); - Assert.True(measurement.Verdict is Verdict.Passed, measurement.Description); - Assert.Same(network, new AddGlueNeuron(library, Factory(network.Neurons.Count, random)).Mutate(network, random)); - } - - [Fact] - public void APartIsSwappedForACheaperOneWithTheSameContractAndKeepsItsWires() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(PartFixtures.PaddedIncrement()), "a test"); - Network padded = Chained(library, increment, 1); - library.AddPart(Verified(ReferenceParts.Increment()), "a cheaper test"); - var swap = new SwapPart(library); - - Network once = swap.Mutate(padded, new Random(1)); - Network twice = swap.Mutate(once, new Random(1)); - - Assert.Equal(new[] { 12, 11, 10 }, new[] { padded, once, twice }.Select(network => network.Neurons.Count)); - Assert.Same(twice, swap.Mutate(twice, new Random(1))); - Assert.Equal(new[] { "1.out>2.n", "1.done>2.start" }, PartWiring.Wires(twice, library).Select(WireText)); - Assert.Equal(new[] { true, true }, twice.Neurons.Take(2).Select(neuron => neuron.IsInput)); - PartMeasurement measurement = MeasurePlusTwo(twice, library); - Assert.True(measurement.Verdict is Verdict.Passed, measurement.Description); - // A role on a neuron with no port of the same name would be lost, so that copy is not swapped. - Network outputOnPadding = padded.WithNeuron(11, padded.Neurons[11].WithRoles(true, false)); - Assert.All(Enumerable.Range(0, 10), seed => Assert.Single(swap.Mutate(outputOnPadding, new Random(seed)).Neurons, neuron => neuron.IsOutput)); - } - - [Fact] - public void APortThatTakesTheOutputSendsNowhereAndAnOldOutputInACopyFeedsNoOtherPort() - { - var library = new ModuleLibrary(); - Module increment = library.AddPart(Verified(IncrementWithOutput()), "a test"); - int takeovers = 0; - - for (int seed = 0; seed < 20; seed++) - { - Network chained = Chained(library, increment, seed); - Network hosted = ModuleEdits.Insert(PingPong(), increment, 1, MaxNeurons, library, new Random(seed)); - - PartCopy first = PartWiring.Copies(chained, library).Single(copy => copy.Tag.Instance == 1); - PartCopy second = PartWiring.Copies(chained, library).Single(copy => copy.Tag.Instance == 2); - PartCopy guest = PartWiring.Copies(hosted, library).Single(); - if (chained.Neurons[second["out"] - 1].IsOutput) - { - takeovers++; - Assert.DoesNotContain(second["out"], chained.Neurons[first["out"] - 1].Connections); - } - if (hosted.Neurons[guest["out"] - 1].IsOutput) - { - Assert.Empty(hosted.Neurons[guest["out"] - 1].Connections); - } - } - Assert.InRange(takeovers, 1, 19); - } - - [Fact] - public void PartEditsAreOnlyOfferedWhenTheLibraryHoldsParts() - { - var harvested = new ModuleLibrary(); - harvested.Add(ModuleCuts.Whole(PingPong()), "a test"); - var withParts = new ModuleLibrary(); - withParts.AddPart(Verified(ReferenceParts.Increment()), "a test"); - NetworkFactory factory = Factory(MaxNeurons, new Random(1)); - - IEnumerable PartEdits(ModuleLibrary library) => - WeightedMutation.Structural(1, factory, modules: new ModuleSupport(library)).Edits.Where(edit => edit.Edit is RewirePort or AddGlueNeuron or SwapPart); - - Assert.Empty(PartEdits(harvested)); - Assert.Equal(3, PartEdits(withParts).Count()); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PromotionTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/PromotionTests.cs deleted file mode 100644 index 9afbced..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/PromotionTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Evolution.Verification; -using SnpEvolution.Networks; -using SnpEvolution.Storage; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; - -namespace SnpEvolution.Tests.Evolution -{ - public sealed class PromotionTests : IDisposable - { - private readonly string folder = Path.Combine(Path.GetTempPath(), "promotion-" + Guid.NewGuid().ToString("N")); - private readonly List log = new List(); - - public void Dispose() - { - if (Directory.Exists(folder)) - { - Directory.Delete(folder, recursive: true); - } - } - - private static readonly PartOrigin Origin = new PartOrigin(1, "a test", 0); - - // k copies of a part with ports n, out, start and done, each draining into the next, read from the last. - private static (Composition Composition, PortBinding Binding) Chain(ModuleLibrary library, Module module, int k) - { - Dictionary ports = module.Part!.Part.Ports().ToDictionary(port => port.Port.Name, port => port.Position); - var composition = new Composition( - Enumerable.Range(1, k).Select(instance => new PartInstance(instance, module.Id, module.Versions.Count - 1)).ToList(), - Array.Empty(), - Enumerable.Range(1, k - 1).SelectMany(instance => new[] { new PortWire(instance, "out", instance + 1, "n"), new PortWire(instance, "done", instance + 1, "start") }).ToList(), - Array.Empty(), - new[] { new Endpoint(1, ports["start"]), new Endpoint(1, ports["n"]) }, - Array.Empty()); - IReadOnlyList layout = composition.Layout(library); - int Position(string port) => layout.ToList().IndexOf(new Endpoint(k, ports[port])) + 1; - return (composition, new PortBinding(new Dictionary { ["out"] = Position("out"), ["done"] = Position("done") })); - } - - private static (ModuleLibrary Library, Module Increment) Increments() - { - var library = new ModuleLibrary(); - return (library, library.AddPart(Verified(ReferenceParts.Increment()), "a test")); - } - - private static string Json(Network network) => NetworkFiles.ToJson(network); - - [Fact] - public void TheTwoIncrementChainPromotedAsAddTwoLoadsFromDiskAndFlattensToTheSameNetwork() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition chain, PortBinding binding) = Chain(library, increment, 2); - - var budget = new EvaluationBudget(); - - Module promoted = Promotion.Promote(chain, ArithmeticParts.AddTwo(), binding, library, Origin, budget, log.Add).Module!; - PartLibraryFiles.Save(library, folder); - ModuleLibrary loaded = PartLibraryFiles.Load(folder); - LibraryPart addTwo = loaded.PartFor("add 2")!.Part!; - - Assert.Equal(Json(chain.Flatten(library)), Json(addTwo.Part.Network)); - Assert.Equal(promoted.Part!.Part.Binding.Positions, addTwo.Part.Binding.Positions); - Assert.Equal(promoted.Part.Cost, addTwo.Cost); - Assert.Contains(log, line => line.StartsWith("Promoted the composition for add 2")); - Assert.True(budget[EvaluationKind.ProofStep] > 0); - } - - // The file names its children and wiring, so a change to a child shows in the child's file alone. - [Fact] - public void APromotedPartIsStoredAsItsChildrenAndWiringRatherThanANetwork() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition chain, PortBinding binding) = Chain(library, increment, 2); - Promotion.Promote(chain, ArithmeticParts.AddTwo(), binding, library, Origin, new EvaluationBudget(), log.Add); - - PartLibraryFiles.Save(library, folder); - string file = File.ReadAllText(Path.Combine(folder, "add-2.json")); - - Assert.Contains("\"Recipe\"", file); - Assert.DoesNotContain("\"Network\"", file); - Assert.Contains("\"Contract\": \"increment\"", file); - Assert.Contains("\"2.out\"", file); - } - - [Fact] - public void ACompositeLargerThanTheLeafCapIsAccepted() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition chain, PortBinding binding) = Chain(library, increment, 5); - - Module? promoted = Promotion.Promote(chain, CatalogueEntry.Of(Specifications.AddConstant(5), FirstParts.Each(FirstParts.Values)).Contract, binding, library, Origin, new EvaluationBudget(), log.Add).Module; - - Assert.NotNull(promoted); - Assert.True(promoted!.Body.Neurons.Count > ModuleLibrary.MaxModuleNeurons); - Assert.True(promoted.Part!.IsComposite); - } - - [Fact] - public void ACompositionThatFailsTheContractIsNotPromoted() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition chain, PortBinding binding) = Chain(library, increment, 2); - - Promoted promoted = Promotion.Promote(chain, FirstParts.Named("increment"), binding, library, Origin, new EvaluationBudget(), log.Add); - - Assert.Equal(ContractRule.DoneOnce, Assert.IsType(promoted.Verdict).Counterexample.Rule); - Assert.Null(promoted.Module); - Assert.Single(library.Parts); - Assert.Contains(log, line => line.Contains("fails the contract")); - } - - // add 4 is two add 2s, each two increments, and its file sorts before the files it needs. - [Fact] - public void PartsBuiltFromPromotedPartsLoadAfterTheirChildren() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition two, PortBinding twoBinding) = Chain(library, increment, 2); - Module addTwo = Promotion.Promote(two, ArithmeticParts.AddTwo(), twoBinding, library, Origin, new EvaluationBudget(), log.Add).Module!; - (Composition four, PortBinding fourBinding) = Chain(library, addTwo, 2); - Promotion.Promote(four, CatalogueEntry.Of(Specifications.AddConstant(4), FirstParts.Each(FirstParts.Values)).Contract, fourBinding, library, Origin, new EvaluationBudget(), log.Add); - - PartLibraryFiles.Save(library, folder); - ModuleLibrary loaded = PartLibraryFiles.Load(folder); - - Assert.Equal(new[] { "add 2", "add 4", "increment" }, loaded.Parts.Select(module => module.Part!.Contract.Name).Order()); - Assert.Equal( - Json(library.PartFor("add 4")!.Part!.Part.Network), - Json(loaded.PartFor("add 4")!.Part!.Part.Network)); - } - - [Fact] - public void APromotedPartWhoseChildIsMissingIsRefusedByName() - { - (ModuleLibrary library, Module increment) = Increments(); - (Composition chain, PortBinding binding) = Chain(library, increment, 2); - Promotion.Promote(chain, ArithmeticParts.AddTwo(), binding, library, Origin, new EvaluationBudget(), log.Add); - PartLibraryFiles.Save(library, folder); - File.Delete(Path.Combine(folder, "increment.json")); - - InvalidDataException refused = Assert.Throws(() => PartLibraryFiles.Load(folder)); - - Assert.Contains("'add-2.json' is built from parts the folder does not have: increment", refused.Message); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ProposalTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/ProposalTests.cs deleted file mode 100644 index 229f8f0..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/ProposalTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Modules; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Proposals; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Simulation; -using Xunit.Abstractions; -using static SnpEvolution.Tests.Evolution.ModuleFixtures; - -namespace SnpEvolution.Tests.Evolution -{ - public class ProposalTests - { - private readonly ITestOutputHelper output; - - public ProposalTests(ITestOutputHelper output) => this.output = output; - - private static IReadOnlyList Parts(ShapeProposal? proposal) => proposal!.Contracts.Select(contract => contract.Name).Order().ToList(); - - [Fact] - public void FibonacciGapsAskForTwoRegistersAndAnAdd() => - Assert.Equal(new[] { "add", "register", "register" }, Parts(RecurrenceProposer.Propose(new[] { 1, 1, 2, 3, 5, 8, 13, 21 }))); - - [Fact] - public void PowersOfTwoAskForARegisterAndADouble() => - Assert.Equal(new[] { "double", "register" }, Parts(RecurrenceProposer.Propose(new[] { 1, 2, 4, 8, 16, 32 }))); - - [Fact] - [Slow] - public void ARandomSequenceAsksForNothing() - { - var random = new Random(31); - for (int attempt = 0; attempt < 20; attempt++) - { - int[] gaps = Enumerable.Range(0, 10).Select(_ => random.Next(1, 40)).ToArray(); - - Assert.Null(RecurrenceProposer.Propose(gaps)); - } - } - - // 2, 4, 6, 8 fits no linear recurrence with coefficients of 0 or more, but its gaps grow by 2 each time. - [Fact] - public void GapsWithAConstantDifferenceAskForARegisterAndAnAdd() - { - ShapeProposal proposal = RecurrenceProposer.Propose(new[] { 2, 4, 6, 8, 10 })!; - - Assert.Equal(new[] { "add", "register" }, Parts(proposal)); - Assert.Equal("every difference of the gaps is 2", proposal.Form); - Assert.Equal(new[] { "increment", "register" }, Parts(RecurrenceProposer.Propose(new[] { 3, 4, 5, 6, 7 }))); - Assert.Null(RecurrenceProposer.Propose(new[] { 10, 8, 6, 4, 2 })); - } - - [Fact] - public void AFailingGapProposesATimerAndFailingCasesASubContract() - { - var sequence = new SequenceTask("gaps", new[] { 2, 5, 3 }); - var task = new ContractTask(FirstParts.Named("increment")); - int caseThree = FirstParts.Values.ToList().IndexOf(3); - - Assert.Equal("delay 5", sequence.Propose(new[] { 1, 2 })!.Name); - Contract sub = task.Propose(new[] { ContractTask.CheckIndex(caseThree, ContractRule.DoneOnce) })!; - Assert.Equal("increment on n=3", sub.Name); - Assert.Equal(4, sub.Cases.Single().Outputs["out"]); - Assert.Null(task.Propose(Enumerable.Range(0, task.Contract.Cases.Count).Select(caseIndex => ContractTask.CheckIndex(caseIndex, ContractRule.OnTime)).ToList())); - } - - // Fibonacci gaps fit a recurrence, so the target's shape asks for its parts before any check is read. - [Fact] - public void ASequenceTargetProposesThePartsItsShapeAsksFor() - { - var random = new Random(3); - var library = new ModuleLibrary(); - library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - var task = new SequenceTask("fibonacci", new[] { 1, 1, 2, 3, 5, 8 }); - var factory = new NetworkFactory(new GenomeSpace(InputCount: 0, RuleForm: RuleForm.Standard, MaxNeurons: 4), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), task, new SimulationOptions(30, 2, OutputTiming.Interval), 1, random, new EvaluationBudget()); - var space = new CompositionSpace(library, factory, new CompositionMix(), random); - IGeneticAlgorithm search = SearchCatalog.CompositionMapElites.Create(new EvolutionContext(12, 0.5f, random, space.NewNetwork, evaluator, factory, _ => { }, Parts: library)); - var proposals = new PartProposals(search, space, () => task, contract => new PartOutcome(contract, 0, new EvaluationBudget().Report(), null, null), - new ProposalPolicy(Patience: 2, MaxProposals: 6), new EvaluationBudget(), 12, _ => { }); - - for (int generation = 0; generation < 12 && proposals.Proposals.Count == 0; generation++) - { - proposals.NextGeneration(); - } - - Assert.Contains(proposals.Proposals, proposal => proposal.Source == ProposalSource.TargetShape); - } - - // A library of one increment cannot make a sequence of gaps, so the run stalls at once; the gap it misses is - // proposed as a delay, evolved and verified like a first part, and added to the library. - [Fact] - public void AStalledRunProposesAPartSolvesItAndAddsIt() - { - var random = new Random(3); - var library = new ModuleLibrary(); - library.AddPart(Verified(ReferenceParts.Increment()), "a test"); - var task = new SequenceTask("gaps", new[] { 1, 6, 1 }); - var factory = new NetworkFactory(new GenomeSpace(InputCount: 0, RuleForm: RuleForm.Standard, MaxNeurons: 4), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); - var evaluator = new FitnessEvaluator(new SequentialCpuEngine(), task, new SimulationOptions(30, 2, OutputTiming.Interval), 1, random, new EvaluationBudget()); - var space = new CompositionSpace(library, factory, new CompositionMix(), random); - var context = new EvolutionContext(12, 0.5f, random, space.NewNetwork, evaluator, factory, _ => { }, Parts: library); - IGeneticAlgorithm search = SearchCatalog.CompositionMapElites.Create(context); - var settings = new PartSearchSettings(30_000, 0, PartsService.Population, Catalog.StructuralDefault, () => new ExhaustiveCpuEngine()); - var log = new List(); - var proposals = new PartProposals(search, space, () => task, contract => PartSearch.Evolve(contract, 1, settings, new EvaluationBudget(), log.Add), - new ProposalPolicy(Patience: 2, MaxProposals: 1), new EvaluationBudget(), 12, log.Add); - - for (int generation = 0; generation < 12 && proposals.Proposals.Count == 0; generation++) - { - proposals.NextGeneration(); - } - - output.WriteLine(proposals.Describe()); - Proposal proposal = Assert.Single(proposals.Proposals); - Assert.Equal(ProposalSource.FailingChecks, proposal.Source); - Assert.Equal(ProposalOutcome.Solved, proposal.Outcome); - Assert.StartsWith("delay ", proposal.Contract.Name); - LibraryPart proposed = library.Find(proposal.Module!.Value)!.Part!; - Assert.Equal(proposal.Contract.Name, proposed.Contract.Name); - Assert.True(proposed.Proven?.AllInputs); - Assert.Contains(log, line => line.StartsWith("Proposal generation") && line.Contains("solved in")); - Assert.Contains("1 solved", proposals.Describe()); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/SearchTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/SearchTests.cs deleted file mode 100644 index 48f9da4..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/SearchTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -using SnpEvolution.Application; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Algorithms; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; -using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class SearchTests - { - private static NetworkFactory Factory(Random random) => - new NetworkFactory(new GenomeSpace(RuleForm: RuleForm.Standard), new ExpressionGenerator(ExpressionGenerator.SimpleTemplates, 4, random), random); - - private static GeneticAlgorithm Generational(int size, Random random, Func create, IPopulationEvaluator evaluator, IMutation mutation) => - new GeneticAlgorithm(size, random, create, evaluator, new GeneticOperators(new TournamentSelection(3), new NeuronCrossover(), mutation), 1, _ => { }); - - [Fact] - public void FindsTheTypoInAFibonacciTarget() - { - SuspectedTypo? typo = TargetPatterns.Find(new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 114, 233, 377, 610, 987 }); - - Assert.NotNull(typo); - Assert.Equal((11, 114, 144), (typo!.Index, typo.Found, typo.Expected)); - } - - [Theory] - [InlineData(new[] { 2, 4, 6, 9, 10 }, 3, 8)] - [InlineData(new[] { 1, 2, 4, 8, 15, 32 }, 4, 16)] - [InlineData(new[] { 7, 1, 2, 3, 5, 8 }, 0, 1)] - public void FindsASingleValueThatBreaksAPattern(int[] values, int index, int expected) - { - SuspectedTypo? typo = TargetPatterns.Find(values); - - Assert.Equal((index, expected), (typo!.Index, typo.Expected)); - } - - [Theory] - [InlineData(new[] { 1, 1, 2, 3, 5, 8, 13 })] - [InlineData(new[] { 3, 1, 4, 1, 5, 9 })] - [InlineData(new[] { 1, 1, 2, 4 })] - public void LeavesTargetsWithoutAClearTypoAlone(int[] values) - { - Assert.Null(TargetPatterns.Find(values)); - } - - [Fact] - public void CurriculumStagesEndWithTheWholeTarget() - { - Assert.Equal(new[] { 3, 4, 5, 6 }, new CurriculumPlan(3, 1).Lengths(6)); - Assert.Equal(new[] { 8, 12, 15 }, new CurriculumPlan(8, 4).Lengths(15)); - Assert.Equal(new[] { 4 }, new CurriculumPlan(10, 2).Lengths(4)); - } - - [Fact] - public void IterativeEvolutionSolvesEachStageInTurn() - { - var random = new Random(1); - var task = new SequenceTask("twos", Enumerable.Repeat(2, 6).ToList()); - var seen = new List(); - var iterative = new IterativeEvolution(task, new[] { 2, 4, 6 }, - stageTask => - { - seen.Add(((SequenceTask)stageTask).Expected.Count); - return new FitnessEvaluator(new SequentialCpuEngine(), stageTask, new SimulationOptions(5, 3, OutputTiming.Interval), 2, random, new EvaluationBudget()); - }, - evaluator => Generational(6, random, PingPong, evaluator, WeightedMutation.Structural(0, Factory(random))), - _ => { }); - - for (int generation = 0; generation < 10 && !iterative.IsComplete; generation++) - { - iterative.NextGeneration(); - } - - Assert.True(iterative.IsComplete); - Assert.Equal(new[] { 2, 4, 6 }, seen); - Assert.Equal(new[] { 2, 4, 6 }, iterative.Stages.Select(stage => stage.Length)); - Assert.All(iterative.Stages, stage => Assert.True(stage.Solved)); - Assert.Equal(4, iterative.Generation); - } - - [Fact] - public void ALuckyScoreDoesNotSolveAStageAndIsReplacedByItsFailedRetest() - { - var lucky = new Individual(AlwaysOutputsOne()); - lucky.Record(new FitnessResult(1f, new[] { 1 })); - var algorithm = new ModularEvolutionTests.StubAlgorithm(); - algorithm.Individuals.Add(lucky); - var iterative = new IterativeEvolution(new SequenceTask("twos", Enumerable.Repeat(2, 4).ToList()), new[] { 2, 4 }, - stageTask => new FitnessEvaluator(new SequentialCpuEngine(), stageTask, new SimulationOptions(5, 3, OutputTiming.Interval), 2, new Random(0), new EvaluationBudget()), - _ => algorithm, - _ => { }); - - iterative.NextGeneration(); - - Assert.False(iterative.Stages.Single().Solved); - Assert.True(lucky.Fitness < 1f); - } - - [Fact] - public void StagnationEscalatesThenRestartsAndCalmsDownOnANewTask() - { - var random = new Random(2); - NetworkFactory factory = Factory(random); - var pressure = new MutationPressure(); - var evaluator = new RecordingEvaluator(_ => 0.5f); - var recovery = new StagnationRecovery( - Generational(8, random, factory.NewNetwork, evaluator, WeightedMutation.Structural(0.5f, factory, pressure)), - new StagnationPolicy(Patience: 2, ImmigrantFraction: 0.25, MaxExtraEdits: 2), 8, pressure, - factory.NewNetwork, WeightedMutation.Structural(1, factory), random, _ => { }); - - recovery.NextGeneration(); - for (int generation = 0; generation < 4; generation++) - { - recovery.NextGeneration(); - } - Assert.Equal((2, 0, 2), (recovery.Escalations, recovery.Restarts, pressure.ExtraEdits)); - - recovery.NextGeneration(); - recovery.NextGeneration(); - Assert.Equal((1, 0), (recovery.Restarts, pressure.ExtraEdits)); - Assert.Equal(8, recovery.Population.Count); - - recovery.NextGeneration(); - recovery.NextGeneration(); - Assert.Equal(1, pressure.ExtraEdits); - recovery.Rescore(); - Assert.Equal(0, pressure.ExtraEdits); - } - - [Fact] - public void PressureMutatesEveryChild() - { - var random = new Random(3); - NetworkFactory factory = Factory(random); - Network network = factory.NewNetwork(); - - Assert.Same(network, WeightedMutation.Structural(0, factory).Mutate(network, random)); - Assert.NotSame(network, WeightedMutation.Structural(0, factory, new MutationPressure { ExtraEdits = 3 }).Mutate(network, random)); - } - - [Fact] - public void GenerationalImmigrantsReplaceTheWeakestButNotTheElite() - { - var random = new Random(4); - NetworkFactory factory = Factory(random); - GeneticAlgorithm algorithm = Generational(6, random, factory.NewNetwork, new RecordingEvaluator(network => 1f / network.Neurons.Count), WeightedMutation.Structural(1, factory)); - algorithm.NextGeneration(); - Network elite = algorithm.Population[0].Genes; - Network[] newcomers = { NeverOutputs(), NeverOutputs(), NeverOutputs() }; - - algorithm.Immigrate(newcomers); - - Assert.Equal(6, algorithm.Population.Count); - Assert.Same(elite, algorithm.Population[0].Genes); - Assert.Equal(newcomers, algorithm.Population.Skip(3).Select(individual => individual.Genes)); - } - - [Fact] - public void AsManyImmigrantsAsThePopulationStillLeaveTheEliteInPlace() - { - var random = new Random(4); - NetworkFactory factory = Factory(random); - var evaluator = new RecordingEvaluator(network => 1f / network.Neurons.Count); - var algorithms = new IGeneticAlgorithm[] - { - Generational(6, random, factory.NewNetwork, evaluator, WeightedMutation.Structural(1, factory)), - new SpeciatedAlgorithm(6, random, factory.NewNetwork, evaluator, new NeuronCrossover(), WeightedMutation.Structural(1, factory)), - }; - foreach (IGeneticAlgorithm algorithm in algorithms) - { - algorithm.NextGeneration(); - Network elite = algorithm.Population[0].Genes; - - algorithm.Immigrate(Enumerable.Range(0, 6).Select(_ => NeverOutputs()).ToList()); - - Assert.Same(elite, algorithm.Population[0].Genes); - Assert.Equal(6, algorithm.Population.Count); - } - } - - [Fact] - public void NewcomersAreMadeOnlyFromScoredHosts() - { - var scored = new Individual(TestNetworks.Identity()); - scored.Record(new FitnessResult(-1, Array.Empty())); - var unscored = new Individual(NeverOutputs()); - - List made = Immigrants.FromBest(new[] { unscored, scored }, hosts: 1, wanted: 1, attempts: 1, host => host); - - Assert.Same(scored.Genes, Assert.Single(made)); - } - - [Fact] - public void AnEvolutionSearchStartsFromItsSeed() - { - var random = new Random(4); - NetworkFactory factory = Factory(random); - var scoring = new NetworkScoring(() => new SequentialCpuEngine(), new SimulationOptions(20, 1, OutputTiming.Interval), 1); - var setup = new NetworkSetup(4, 0.5f, factory, () => throw new InvalidOperationException("The seed should be used."), scoring); - var seed = new Individual(TestNetworks.Identity()); - - SearchOutcome outcome = SearchCatalog.StructuralDefault.Run( - new SearchRequest(FunctionTask.Of("n", n => n, new[] { 1, 2 }), new EvaluationBudget(), random, _ => { }) { Seeds = new[] { seed }, MaxGenerations = 1, Networks = setup }); - - Assert.Same(seed.Genes, outcome.Best!.Genes); - } - - [Fact] - public void ArchiveAndStrategyImmigrantsJoinTheNextBatch() - { - var random = new Random(5); - NetworkFactory factory = Factory(random); - var evaluator = new RecordingEvaluator(network => 1f / network.Neurons.Count); - var algorithms = new IGeneticAlgorithm[] - { - new MapElites(6, random, factory.NewNetwork, evaluator, new NeuronCrossover(), WeightedMutation.Structural(1, factory)), - new MuPlusLambdaStrategy(2, 6, random, factory.NewNetwork, evaluator, WeightedMutation.Structural(1, factory)), - }; - foreach (IGeneticAlgorithm algorithm in algorithms) - { - algorithm.NextGeneration(); - Network newcomer = NeverOutputs(); - algorithm.Immigrate(new[] { newcomer }); - evaluator.Seen.Clear(); - - algorithm.NextGeneration(); - - Assert.Contains(newcomer, evaluator.Seen); - Assert.Equal(6, evaluator.Seen.Count); - } - } - - [Fact] - public void RescoringTheArchiveUsesTheNewTask() - { - var random = new Random(6); - NetworkFactory factory = Factory(random); - var evaluator = new RecordingEvaluator(_ => 0.9f); - var elites = new MapElites(8, random, factory.NewNetwork, evaluator, new NeuronCrossover(), WeightedMutation.Structural(1, factory)); - elites.NextGeneration(); - int cells = elites.Population.Count; - - evaluator.Fitness = _ => 0.2f; - elites.Rescore(); - - Assert.Equal(0.2f, elites.Best!.Fitness); - Assert.Equal(cells, elites.Population.Count); - Assert.All(elites.Population, elite => Assert.Equal(0.2f, elite.Fitness)); - } - - [Fact] - public void BehaviourNichesReplaceSizeCellsWhenTheTaskHasThem() - { - var individual = new Individual(Identity()); - individual.Record(new FitnessResult(0.5f, Array.Empty())); - Assert.Equal((false, 2, 2), MapElites.CellOf(individual)); - - individual.Record(new FitnessResult(0.5f, Array.Empty(), Niche: (3, 4))); - Assert.Equal((true, 3, 4), MapElites.CellOf(individual)); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/StructuralOperatorTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/StructuralOperatorTests.cs deleted file mode 100644 index b68195f..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/StructuralOperatorTests.cs +++ /dev/null @@ -1,154 +0,0 @@ -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Operators; -using SnpEvolution.Evolution.Search; -using SnpEvolution.Networks; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class StructuralOperatorTests - { - private static NetworkFactory Factory(int seed, int inputCount = 1, RuleForm form = RuleForm.Mixed) => - new NetworkFactory(new GenomeSpace(InputCount: inputCount, RuleForm: form, MaxNeurons: 6, DuplicateNeurons: true), - new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, new Random(seed)), new Random(seed)); - - private static void AssertWellFormed(Network network, GenomeSpace space) - { - Assert.Single(network.Neurons, neuron => neuron.IsOutput); - Assert.Equal(space.InputCount, network.Neurons.Count(neuron => neuron.IsInput)); - Assert.InRange(network.Neurons.Count, space.SmallestNetwork, space.MaxNeurons); - for (int position = 1; position <= network.Neurons.Count; position++) - { - Neuron neuron = network.Neurons[position - 1]; - Assert.NotEmpty(neuron.Rules); - Assert.InRange(neuron.Rules.Count, 1, space.MaxRulesPerNeuron); - Assert.DoesNotContain(position, neuron.Connections); - Assert.All(neuron.Connections, target => Assert.InRange(target, 1, network.Neurons.Count)); - Assert.Equal(neuron.Connections.Distinct().OrderBy(target => target), neuron.Connections); - Assert.All(neuron.Rules.Where(rule => rule.IsStandard), rule => Assert.True(rule.Consume >= 1 && rule.Produce >= 1)); - Assert.True(neuron.InitialSpikes >= 0); - Assert.True(!neuron.IsInput || neuron.InitialSpikes == 0); - } - } - - [Fact] - public void RandomNetworksAreWellFormed() - { - for (int seed = 0; seed < 100; seed++) - { - NetworkFactory factory = Factory(seed, inputCount: seed % 3); - AssertWellFormed(factory.NewNetwork(), factory.Space); - } - } - - [Fact] - public void EveryEditKeepsNetworksWellFormed() - { - for (int seed = 0; seed < 30; seed++) - { - NetworkFactory factory = Factory(seed, inputCount: seed % 3); - WeightedMutation mutation = WeightedMutation.Structural(1, factory); - var crossover = new NeuronCrossover(); - var random = new Random(seed); - Network network = factory.NewNetwork(); - for (int step = 0; step < 200; step++) - { - network = random.Next(5) == 0 - ? crossover.Cross(network, factory.NewNetwork(), random) - : mutation.Edits[random.Next(mutation.Edits.Count)].Edit.Mutate(network, random); - AssertWellFormed(network, factory.Space); - } - } - } - - [Fact] - public void DuplicateNeuronCopiesANeuronAndItsIncomingSynapses() - { - NetworkFactory factory = Factory(7, inputCount: 0); - Network network = new Network(new[] - { - Neuron(2, new[] { 2 }, Standard("aa", 2)), - Neuron(0, new[] { 3 }, Standard("a", 1)), - OutputNeuron(0, Standard("a", 1)), - }); - - Network duplicated = Enumerable.Range(0, 20).Select(seed => new DuplicateNeuron(factory.Space).Mutate(network, new Random(seed))) - .First(candidate => candidate.Neurons[^1].Rules[0].Expression == "a" && candidate.Neurons[^1].Connections.SequenceEqual(new[] { 3 })); - - Assert.Equal(4, duplicated.Neurons.Count); - Assert.False(duplicated.Neurons[3].IsOutput); - Assert.Equal(new[] { 2, 4 }, duplicated.Neurons[0].Connections); - AssertWellFormed(duplicated, factory.Space with { InputCount = 0 }); - } - - [Fact] - public void DuplicationIsOnlyOfferedWhenAllowed() - { - NetworkFactory allowed = Factory(1); - var notAllowed = new NetworkFactory(allowed.Space with { DuplicateNeurons = false }, new ExpressionGenerator(ExpressionGenerator.SimpleTemplates, 4, new Random(1)), new Random(1)); - - Assert.Contains(WeightedMutation.Structural(1, allowed).Edits, edit => edit.Edit is DuplicateNeuron); - Assert.DoesNotContain(WeightedMutation.Structural(1, notAllowed).Edits, edit => edit.Edit is DuplicateNeuron); - } - - [Fact] - public void StandardSpaceOnlyMakesStandardRules() - { - NetworkFactory factory = Factory(1, form: RuleForm.Standard); - - Assert.All(Enumerable.Range(0, 50).Select(_ => factory.NewRule()), rule => Assert.True(rule.IsStandard)); - } - - [Fact] - public void NudgeExpressionChangesOneRunOfSpikesByOne() - { - Network network = new Network(new[] { OutputNeuron(0, new Rule("aa(aaa)*", 0, true)) }); - var seen = new HashSet(); - - for (int seed = 0; seed < 40; seed++) - { - seen.Add(new NudgeExpression().Mutate(network, new Random(seed)).Neurons[0].Rules[0].Expression); - } - - Assert.Equal(new HashSet { "a(aaa)*", "aaa(aaa)*", "aa(aa)*", "aa(aaaa)*" }, seen); - } - - [Fact] - public void SplitSynapseInsertsARelayInPlaceOfTheSynapse() - { - NetworkFactory factory = Factory(0, inputCount: 0, form: RuleForm.Standard); - - Network split = new SplitSynapse(factory).Mutate(Identity(), new Random(0)); - - Assert.Equal(3, split.Neurons.Count); - Assert.Equal(new[] { 3 }, split.Neurons[0].Connections); - Assert.Equal(new[] { 2 }, split.Neurons[2].Connections); - Assert.Equal("a -> a", NetworkNotation.Rule(split.Neurons[2].Rules[0])); - } - - [Fact] - public void RemoveNeuronRenumbersTheRemainingSynapses() - { - var network = new Network(new[] - { - Neuron(1, new[] { 2, 3 }, new Rule("a", 0, true)), - Neuron(1, new[] { 3 }, new Rule("a", 0, true)), - OutputNeuron(0, new Rule("a", 0, true)), - }); - - Network removed = NetworkEdits.RemoveNeuron(network, 1); - - Assert.Equal(2, removed.Neurons.Count); - Assert.Equal(new[] { 2 }, removed.Neurons[0].Connections); - } - - [Fact] - public void WeightedMutationRespectsItsRate() - { - Network network = Identity(); - var mutation = new WeightedMutation(0, new[] { new WeightedEdit("nudge", new NudgeExpression(), 1) }); - - Assert.Same(network, mutation.Mutate(network, new Random(0))); - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Evolution/TaskTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Evolution/TaskTests.cs deleted file mode 100644 index 0c518bb..0000000 --- a/SNP_Evolution/SNP_Evolution.Tests/Evolution/TaskTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Benchmarking; -using SnpEvolution.Evolution.Fitness; -using SnpEvolution.Evolution.Tasks; -using SnpEvolution.Networks; -using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; - -namespace SnpEvolution.Tests.Evolution -{ - public class TaskTests - { - private static readonly SimulationOptions Options = new SimulationOptions(MaxSteps: 40, Repetitions: 20, OutputTiming.Interval); - - private static FitnessResult Evaluate(ITask task, Network network) => - new FitnessEvaluator(new ExhaustiveCpuEngine(), task, Options, solvedRetestCount: 3, new Random(0), new EvaluationBudget()).Evaluate(network); - - private static TrialResult Outputs(params int[] outputs) => new TrialResult(outputs, false, TrialCoverage.Exact); - - private static TrialResult Halts(bool halts) => new TrialResult(Array.Empty(), halts, TrialCoverage.Exact); - - [Fact] - public void IdentityNetworkSolvesTheIdentityFunctionExactly() - { - FitnessResult result = Evaluate(FunctionTask.Of("n", n => n, Enumerable.Range(1, 6)), Identity()); - - Assert.Equal(1f, result.Fitness); - Assert.True(result.Exact); - Assert.Contains("f(3)={3}/3", result.Description); - } - - [Fact] - public void IdentityNetworkIsCloseButWrongForTheSuccessor() - { - float fitness = Evaluate(FunctionTask.Of("n + 1", n => n + 1, Enumerable.Range(1, 6)), Identity()).Fitness; - - Assert.Equal(0.25f, fitness, precision: 5); - } - - [Fact] - public void FunctionScoreGivesFullCreditOnlyForTheRightOutput() - { - var task = new FunctionTask("f", new[] { new FunctionExample(new[] { 2 }, 4), new FunctionExample(new[] { 3 }, 6) }); - - Assert.Equal(1f, task.Score(new[] { Outputs(4), Outputs(6) })); - Assert.Equal(0.5f, task.Score(new[] { Outputs(4), Outputs() })); - Assert.Equal(0.78125f, task.Score(new[] { Outputs(4, 4), Outputs(6, 9) }), precision: 5); - } - - [Fact] - public void TwoArgumentFunctionsNeedTwoInputs() - { - FunctionTask task = FunctionTask.Of("add", (first, second) => first + second, new[] { (1, 2) }); - - Assert.Equal(2, task.InputCount); - Assert.Equal(new[] { 0, 2 }, task.Cases[0].Input.StepsPerInput[1]); - } - - [Fact] - public void AcceptorScoreIsBalancedAccuracy() - { - AcceptorTask task = AcceptorTask.Of("even", n => n % 2 == 0, new[] { 1, 2, 3, 4, 5, 6 }); - - Assert.Equal(1f, task.Score(new[] { false, true, false, true, false, true }.Select(Halts).ToList())); - Assert.Equal(0.5f, task.Score(Enumerable.Repeat(Halts(true), 6).ToList())); - Assert.Equal(0.5f, task.Score(Enumerable.Repeat(Halts(false), 6).ToList())); - Assert.Equal(0.5f, AcceptorTask.Of("big", n => n >= 4, new[] { 1, 2, 3, 4 }).Score(Enumerable.Repeat(Halts(true), 4).ToList())); - Assert.Equal(Readout.Halting, task.Cases[0].Readout); - } - - [Fact] - public void AcceptorThatAlwaysHaltsAcceptsEverything() - { - var network = new Network(new[] { InputNeuron(Array.Empty(), StandardForget("a", 1)), OutputNeuron(0, Standard("a", 1)) }); - - FitnessResult result = Evaluate(AcceptorTask.Of("big", n => n >= 3, Enumerable.Range(1, 4)), network); - - Assert.Equal(0.5f, result.Fitness); - Assert.Equal("accepts {1,2,3,4}", result.Description); - } - - [Fact] - public void GeneratorTaskScoresWithItsFitnessFunction() - { - var task = new GeneratorTask("{1}", new[] { 1 }, new JaccardFitness(new[] { 1 })); - - FitnessResult result = Evaluate(task, AlwaysOutputsOne()); - - Assert.Equal(1f, result.Fitness); - Assert.Equal("{1}", result.Description); - Assert.Equal(new[] { 0f, 1f, 0f }, new GeneratorTask("{1,2,3}", new[] { 1, 2, 3 }, new JaccardFitness(new[] { 1, 2, 3 })).Checks(new[] { 2 })); - } - - [Fact] - public void ExactResultIsReliablySolvedWithoutRetesting() - { - var budget = new EvaluationBudget(); - var evaluator = new FitnessEvaluator(new ExhaustiveCpuEngine(), FunctionTask.Of("n", n => n, new[] { 1, 2 }), Options, solvedRetestCount: 5, new Random(0), budget); - - Assert.True(evaluator.ConfirmSolved(Identity()).Solved); - Assert.Equal(1, budget.Networks); - } - - [Fact] - public void SuiteTasksAreAllUsableWithTheirOwnSettings() - { - foreach (BenchmarkTask task in TaskSuite.All) - { - Assert.NotEmpty(task.Task.Cases); - FitnessResult result = Evaluate(task.Task, Identity()); - Assert.InRange(result.Fitness, 0, 1); - } - } - } -} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Export/NirExporterTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Export/NirExporterTests.cs index cc1eaba..eef35e9 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Export/NirExporterTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Export/NirExporterTests.cs @@ -1,52 +1,46 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; using SnpEvolution.Export; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search.Genome; using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; +using static SnpEvolution.Tests.Fixtures.TestNetworks; namespace SnpEvolution.Tests.Export { public class NirExporterTests { // Parts evolve-parts --profile hardware found, kept in the repository next to parts/. - private static Part ProfilePart(string file) - { - return RepositoryFiles.ReadPart("parts-profile", file).Part; - } + private const string ProfileFolder = "parts-profile"; + + private static readonly string[] ProfilePartFiles = { "delay-2.json", "sequencer-2.json" }; + + public static TheoryData ProfileFiles => new TheoryData(ProfilePartFiles); private static string Check(NirDescription description) { - string folder = Path.Combine(Path.GetTempPath(), "snp-nir-" + Guid.NewGuid().ToString("N")); - try - { - return NirExporter.WriteAndCheck(description, folder); - } - finally - { - Directory.Delete(folder, recursive: true); - } + using var temp = new TempFolder("snp-nir"); + return NirExporter.WriteAndCheck(description, temp.Path); } - [Fact] - public void TheProfilePartsMeetTheirContractsAndFitTheProfile() + [Theory] + [MemberData(nameof(ProfileFiles))] + public void TheProfilePartsMeetTheirContractsAndFitTheProfile(string file) { - foreach (string file in new[] { "delay-2.json", "sequencer-2.json" }) - { - Part part = ProfilePart(file); - Assert.True(Verifier.Measure(part, new EvaluationBudget()).Verdict is Verdict.Passed, file); - Assert.Empty(HardwareProfile.Problems(part.Network)); - } + Part part = RepositoryFiles.Part(ProfileFolder, file); + + Assert.True(Verifier.Measure(part, new EvaluationBudget()).Verdict is Verdict.Passed, file); + Assert.Empty(HardwareProfile.Problems(part.Network)); } + // Stays a loop over the files, since the attribute that skips it without the NIR tools is a fact. [NirFact] public void ProfilePartsLoadInNirAndMatchOurTracesInNorse() { - foreach (string file in new[] { "delay-2.json", "sequencer-2.json" }) + foreach (string file in ProfilePartFiles) { - string report = Check(NirExporter.Export(ProfilePart(file))); + string report = Check(NirExporter.Export(RepositoryFiles.Part(ProfileFolder, file))); Assert.Contains("matches SN P on all 1 case(s)", report); } @@ -58,7 +52,7 @@ public void RandomProfileNetworksMatchOurTracesInNorse() { var random = new Random(3); var space = new GenomeSpace(InputCount: 2, MaxNeurons: 7, MaxDelay: 3, HardwareProfile: true); - var factory = new NetworkFactory(space, new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); + var factory = Factories.Networks(space, random); var input = new InputSpikes(new IReadOnlyList[] { new[] { 0, 1, 1, 2, 5, 6, 6, 6 }, new[] { 0, 3, 4, 4, 9 } }); int firings = 0, delays = 0, forgetting = 0; for (int network = 0; network < 4; network++) diff --git a/SNP_Evolution/SNP_Evolution.Tests/Export/UppaalExporterTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Export/UppaalExporterTests.cs index b8b23e0..825e050 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Export/UppaalExporterTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Export/UppaalExporterTests.cs @@ -1,32 +1,26 @@ using System.Xml; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Tasks; using SnpEvolution.Export; -using SnpEvolution.Networks; -using SnpEvolution.Tests.Evolution; +using SnpEvolution.Model; +using SnpEvolution.Specs.Contracts; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Tasks; namespace SnpEvolution.Tests.Export { public class UppaalExporterTests { - private static Part LibraryPart(string folder, string file) => RepositoryFiles.ReadPart(folder, file).Part; - - private static string Folder() => Path.Combine(Path.GetTempPath(), "snp-uppaal-" + Guid.NewGuid().ToString("N")); + private const string TraceQueryName = "every neuron holds what our engine's run holds, on every step"; + private const string DoneOnceQueryName = "done once, the right one, with the right outputs"; private static IReadOnlyList Check(Part part) { - string folder = Folder(); - try - { - return Verifyta.Check(UppaalExporter.Export(part), folder); - } - finally - { - Directory.Delete(folder, recursive: true); - } + using var temp = new TempFolder("snp-uppaal"); + return Verifyta.Check(UppaalExporter.Export(part), temp.Path); } + private static IReadOnlyList CasesUpTo(Contract contract, int largest) => + Enumerable.Range(0, largest + 1).Select(n => Specification.For(contract)!.Expected(new Dictionary { ["n"] = n })).ToList(); + private static bool Holds(Part part, string query) { UppaalModel model = UppaalExporter.Export(part); @@ -36,14 +30,14 @@ private static bool Holds(Part part, string query) [Fact] public void ExportsWellFormedXmlWithAQueryPerContractRule() { - UppaalModel model = UppaalExporter.Export(LibraryPart("parts", "delay-2.json")); + UppaalModel model = UppaalExporter.Export(RepositoryFiles.Part("parts", "delay-2.json")); using var reader = XmlReader.Create(new StringReader(model.Model), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); while (reader.Read()) { } Assert.Equal("delay-2", model.Name); - Assert.Equal(UppaalExporter.ContractQueries.Select(query => query.Name).Append("every neuron holds what our engine's run holds, on every step"), model.QueryNames); + Assert.Equal(UppaalExporter.ContractQueries.Select(query => query.Name).Append(TraceQueryName), model.QueryNames); Assert.Contains(UppaalExporter.TraceQuery, model.Queries); Assert.Contains("system Clock, N1, N2;", model.Model); } @@ -51,7 +45,7 @@ public void ExportsWellFormedXmlWithAQueryPerContractRule() [Fact] public void TheRegisterModelStartsAndReadsEachCaseAsTheEngineDoes() { - Part register = HandBuiltParts.All().Single(part => part.Contract.Name == "register"); + Part register = PartFixtures.HandBuiltRegister(); var task = new ContractTask(register.Contract, register.Binding); string model = UppaalExporter.Export(register).Model; @@ -95,14 +89,14 @@ public void RefusesALegacyRuleWithADelay() [VerifytaFact] public void TheEvolvedDelayMeetsEveryContractRuleInUppaal() { - Assert.All(Check(LibraryPart("parts", "delay-2.json")), Assert.True); + Assert.All(Check(RepositoryFiles.Part("parts", "delay-2.json")), Assert.True); } [VerifytaFact] public void TheEvolvedSequencerAndProfileDelayMeetEveryContractRuleInUppaal() { - Assert.All(Check(LibraryPart("parts", "sequencer-2.json")), Assert.True); - Assert.All(Check(LibraryPart("parts-profile", "delay-2.json")), Assert.True); + Assert.All(Check(RepositoryFiles.Part("parts", "sequencer-2.json")), Assert.True); + Assert.All(Check(RepositoryFiles.Part("parts-profile", "delay-2.json")), Assert.True); } [VerifytaFact] @@ -110,23 +104,23 @@ public void UppaalFindsADelayThatFiresDoneTwice() { Part twice = PartFixtures.DelayFiringDoneTwice(2); - Assert.False(Holds(twice, "done once, the right one, with the right outputs")); - Assert.True(Holds(twice, "every neuron holds what our engine's run holds, on every step")); + Assert.False(Holds(twice, DoneOnceQueryName)); + Assert.True(Holds(twice, TraceQueryName)); } [VerifytaFact] public void TheHandBuiltRegisterMeetsEveryContractRuleOnEveryCaseInUppaal() { - Assert.All(Check(HandBuiltParts.All().Single(part => part.Contract.Name == "register")), Assert.True); + Assert.All(Check(PartFixtures.HandBuiltRegister()), Assert.True); } [VerifytaFact] public void UppaalAgreesWithTheBoundedCheckOnARegisterFailingAtTwenty() { - Part broken = BoundedCheckTests.RegisterFailingAtTwenty(); - Contract upToTwenty = broken.Contract with { Cases = Enumerable.Range(0, 21).Select(n => Specification.For(broken.Contract)!.Expected(new Dictionary { ["n"] = n })).ToList(), MaxLatency = Specifications.LatencyFor(20) }; + Part broken = PartFixtures.RegisterFailingAtTwenty(); + Contract upToTwenty = broken.Contract with { Cases = CasesUpTo(broken.Contract, 20), MaxLatency = Specifications.LatencyFor(20) }; - Assert.False(Holds(broken with { Contract = upToTwenty }, "done once, the right one, with the right outputs")); + Assert.False(Holds(broken with { Contract = upToTwenty }, DoneOnceQueryName)); } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Export/VerilogExporterTests.cs b/SNP_Evolution/SNP_Evolution.Tests/Export/VerilogExporterTests.cs index 4dad73d..e377b1e 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Export/VerilogExporterTests.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Export/VerilogExporterTests.cs @@ -1,19 +1,19 @@ -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Contracts; -using SnpEvolution.Evolution.Genome; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; using SnpEvolution.Export; -using SnpEvolution.Networks; +using SnpEvolution.Model; +using SnpEvolution.Search.Genome; using SnpEvolution.Simulation; -using static SnpEvolution.Tests.TestNetworks; +using SnpEvolution.Specs.Accounting; +using SnpEvolution.Specs.Parts; +using SnpEvolution.Specs.Verification; +using static SnpEvolution.Tests.Fixtures.TestNetworks; namespace SnpEvolution.Tests.Export { public class VerilogExporterTests { - private static LibraryPart LibraryDelay() => - RepositoryFiles.ReadPart("parts", "delay-2.json"); + private const int RandomNetworksToExport = 25; + private const int MostRandomNetworksTried = 2000; + private const int RandomInputSteps = 40; // Runs every case of the part under iverilog and returns what it printed and what our engine says it should. private static (string Simulated, string Expected) CoSimulate(Part part) @@ -22,22 +22,15 @@ private static (string Simulated, string Expected) CoSimulate(Part part) var cases = SpikeTrace.Cases(part.Contract); List inputs = cases.Select(@case => @case.Input).ToList(); List steps = cases.Select(@case => @case.Steps).ToList(); - string folder = Path.Combine(Path.GetTempPath(), "snp-verilog-" + Guid.NewGuid().ToString("N")); - try - { - return (Iverilog.Simulate(design, VerilogTestbench.For(design, inputs, steps), folder), VerilogTestbench.ExpectedOutput(part.Network, inputs, steps)); - } - finally - { - Directory.Delete(folder, recursive: true); - } + using var temp = new TempFolder("snp-verilog"); + return (Iverilog.Simulate(design, VerilogTestbench.For(design, inputs, steps), temp.Path), VerilogTestbench.ExpectedOutput(part.Network, inputs, steps)); } public static TheoryData Parts() => new TheoryData { "library delay 2", "register", "add", "increment" }; private static Part Named(string name) => name switch { - "library delay 2" => LibraryDelay().Part, + "library delay 2" => RepositoryFiles.Part("parts", "delay-2.json"), "register" => PartFixtures.Register(), "add" => ReferenceParts.Add(), _ => ReferenceParts.Increment(), @@ -57,7 +50,7 @@ public void PartsMatchOurEngineStepForStepOnEveryContractCase() { (string simulated, string expected) = CoSimulate(Named(name)); - Assert.True(expected == simulated, $"{name} differs from our engine first at: {FirstDifference(expected, simulated)}"); + Assert.True(expected == simulated, $"{name} differs from our engine. {TextDifference.FirstDifference(expected, simulated)}"); Assert.EndsWith("overflow 0\n", simulated); } } @@ -69,11 +62,11 @@ public void RandomDeterministicNetworksMatchOurEngineStepForStep() { var random = new Random(5); var space = new GenomeSpace(InputCount: 2, RuleForm: RuleForm.Mixed, MaxNeurons: 6, MaxDelay: 3, MaxRulesPerNeuron: 2); - var factory = new NetworkFactory(space, new ExpressionGenerator(ExpressionGenerator.ExperimentalTemplates, 4, random), random); + var factory = Factories.Networks(space, random); var input = new InputSpikes(new IReadOnlyList[] { new[] { 0, 1, 1, 4, 9 }, new[] { 2, 3, 7 } }); int exported = 0; var delays = new HashSet(); - for (int attempt = 0; exported < 25 && attempt < 2000; attempt++) + for (int attempt = 0; exported < RandomNetworksToExport && attempt < MostRandomNetworksTried; attempt++) { Network network = random.Next(3) == 0 ? AxonalCopy(factory.NewNetwork(), random) : factory.NewNetwork(); if (SpikeTrace.Choices(network).Count > 0) @@ -82,21 +75,14 @@ public void RandomDeterministicNetworksMatchOurEngineStepForStep() } exported++; delays.UnionWith(network.Neurons.SelectMany(neuron => neuron.Rules).Where(rule => rule.Delay > 0).Select(rule => rule.DelayKind)); - long mostHeld = SpikeTrace.Run(network, input, 40).MostHeld; + long mostHeld = SpikeTrace.Run(network, input, RandomInputSteps).MostHeld; VerilogDesign design = VerilogExporter.Export(network, $"random {attempt}", NetworkPort.Plain(network), mostHeld); - string folder = Path.Combine(Path.GetTempPath(), "snp-verilog-" + Guid.NewGuid().ToString("N")); - try - { - string simulated = Iverilog.Simulate(design, VerilogTestbench.For(design, new[] { input }, new[] { 40 }), folder); - string expected = VerilogTestbench.ExpectedOutput(network, new[] { input }, new[] { 40 }); - Assert.True(expected == simulated, $"{NetworkNotation.Format(network)}\ndiffers first at: {FirstDifference(expected, simulated)}"); - } - finally - { - Directory.Delete(folder, recursive: true); - } + using var temp = new TempFolder("snp-verilog"); + string simulated = Iverilog.Simulate(design, VerilogTestbench.For(design, new[] { input }, new[] { RandomInputSteps }), temp.Path); + string expected = VerilogTestbench.ExpectedOutput(network, new[] { input }, new[] { RandomInputSteps }); + Assert.True(expected == simulated, $"{NetworkNotation.Format(network)}\ndiffers from our engine. {TextDifference.FirstDifference(expected, simulated)}"); } - Assert.Equal(25, exported); + Assert.Equal(RandomNetworksToExport, exported); Assert.Equal(new[] { DelayKind.Closing, DelayKind.Holding, DelayKind.Axonal }, delays.OrderBy(kind => kind)); } @@ -110,14 +96,6 @@ public void RefusesANondeterministicNetworkAndSaysWhichRulesCompete() Assert.Contains("Neuron 1 could apply rule 1 (a+/a -> a) or rule 2 (aa(a)*/aa -> a) when it holds 2 spike(s)", refusal.Message); } - [Fact] - public void FindsRulesThatOnlyCompeteOnceTheNeuronHoldsWhatTheyConsume() - { - var network = new Network(new[] { OutputNeuron(0, Standard("a+", 5), Standard("a+", 5, produce: 2)) }); - - Assert.Contains("when it holds 5 spike(s)", Assert.Single(SpikeTrace.Choices(network))); - } - [Fact] public void SizesCountersFromTheRegisterWidth() { @@ -141,13 +119,5 @@ public void ExportsTheSameTextEveryTime() private static Network AxonalCopy(Network network, Random random) => new Network(network.Neurons.Select(neuron => neuron.WithRules(neuron.Rules.Select(rule => rule.Delay > 0 && random.Next(2) == 0 ? rule.WithAxonal(true) : rule))).ToList()); - - private static string FirstDifference(string expected, string simulated) - { - string[] want = expected.Split('\n'); - string[] got = simulated.Split('\n'); - int line = Enumerable.Range(0, Math.Min(want.Length, got.Length)).FirstOrDefault(index => want[index] != got[index], Math.Min(want.Length, got.Length)); - return $"line {line + 1}: expected '{want.ElementAtOrDefault(line)}', got '{got.ElementAtOrDefault(line)}'"; - } } } diff --git a/SNP_Evolution/SNP_Evolution.Tests/Fixtures/BenchmarkFixtures.cs b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/BenchmarkFixtures.cs new file mode 100644 index 0000000..222b4dd --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/BenchmarkFixtures.cs @@ -0,0 +1,13 @@ +using SnpEvolution.Search.Benchmarking; +using SnpEvolution.Specs.Parts; + +namespace SnpEvolution.Tests.Fixtures +{ + internal static class BenchmarkFixtures + { + // One seed per algorithm and task, so a benchmark test stays quick. + public static readonly BenchmarkSettings OneSeed = BenchmarkSettings.Default with { Seeds = 1 }; + + public static BenchmarkSettings WithParts(ModuleLibrary library) => OneSeed with { Parts = library.Parts.Select(module => module.Part!).ToList() }; + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Fixtures/CatalogEntries.cs b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/CatalogEntries.cs new file mode 100644 index 0000000..b5162a1 --- /dev/null +++ b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/CatalogEntries.cs @@ -0,0 +1,11 @@ +using SnpEvolution.Application; +using SnpEvolution.Simulation; + +namespace SnpEvolution.Tests.Fixtures +{ + internal static class CatalogEntries + { + // One thread keeps a run's evaluation order, and so its counts, the same on every machine. + public static CatalogEntry SingleThreadEngine => Catalog.Engines.Single(engine => engine.Name == "CPU, single thread"); + } +} diff --git a/SNP_Evolution/SNP_Evolution.Tests/Golden/CommandRun.cs b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/CommandRun.cs similarity index 74% rename from SNP_Evolution/SNP_Evolution.Tests/Golden/CommandRun.cs rename to SNP_Evolution/SNP_Evolution.Tests/Fixtures/CommandRun.cs index 41848f7..497a27b 100644 --- a/SNP_Evolution/SNP_Evolution.Tests/Golden/CommandRun.cs +++ b/SNP_Evolution/SNP_Evolution.Tests/Fixtures/CommandRun.cs @@ -2,20 +2,20 @@ using System.Text; using System.Text.RegularExpressions; using SnpEvolution.Cli; -using SnpEvolution.Evolution.Accounting; -using SnpEvolution.Evolution.Parts; -using SnpEvolution.Evolution.Verification; +using SnpEvolution.Specs.Parts; using SnpEvolution.Storage; -namespace SnpEvolution.Tests.Golden +namespace SnpEvolution.Tests.Fixtures { - // Swaps the process-wide console, working directory and culture, so tests using it belong to GoldenCollection. + // Swaps the process-wide console, working directory and culture, so tests using it belong to ProcessStateCollection. internal sealed partial class CommandRun : IDisposable { // Pictures of a network, which only lay out what the .json and .txt files already hold. private static readonly string[] Drawings = { ".html", ".svg" }; - public string Folder { get; } = Path.Combine(Path.GetTempPath(), "snp-golden-" + Guid.NewGuid().ToString("N")); + private readonly TempFolder folder = new TempFolder("snp-golden"); + + public string Folder => folder.Path; // The .git marker makes the folder the root the commands find runs/ and parts/ from, wherever the temp folder lies. public CommandRun() @@ -40,7 +40,7 @@ public void CopyFile(string repositoryFile) // Saves a hand-built part as a library file, measured the way evolve-parts measures the parts it saves. public void Save(Part part, string file) => - File.WriteAllText(Path.Combine(Folder, file), PartLibraryFiles.ToJson(Verifier.Measure(part, new EvaluationBudget()).ToLibraryPart(part, new PartOrigin(0, HandBuiltParts.Origin, 0)))); + File.WriteAllText(Path.Combine(Folder, file), PartLibraryFiles.ToJson(PartFixtures.Measured(part, new PartOrigin(0, HandBuiltParts.Origin, 0)))); // The command line, exit code, output and every file the run left in the folder. public string Run(params string[] args) @@ -57,23 +57,18 @@ public string Run(params string[] args) private (int Exit, string Printed, string Errors) RunInFolder(string[] args) { - TextWriter output = Console.Out, error = Console.Error; string directory = Directory.GetCurrentDirectory(); CultureInfo culture = CultureInfo.CurrentCulture; - var printed = new StringWriter { NewLine = "\n" }; - var errors = new StringWriter { NewLine = "\n" }; + using var console = new ConsoleCapture(); try { - Console.SetOut(printed); - Console.SetError(errors); Directory.SetCurrentDirectory(Folder); CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - return (CommandLine.Run(args), printed.ToString(), errors.ToString()); + int exit = CommandLine.Run(args); + return (exit, console.Printed, console.Errors); } finally { - Console.SetOut(output); - Console.SetError(error); Directory.SetCurrentDirectory(directory); CultureInfo.CurrentCulture = culture; } @@ -96,22 +91,16 @@ private void AppendFiles(StringBuilder text) private string Normalise(string text) { // On macOS the temporary folder is reached through /private as well. - foreach (string folder in new[] { Path.Combine("/private", Folder.TrimStart('/')), Folder }) + foreach (string spelling in new[] { Path.Combine("/private", Folder.TrimStart('/')), Folder }) { - text = text.Replace(folder, ""); + text = text.Replace(spelling, ""); } text = RunFolder().Replace(text, "runs/"); text = Seconds().Replace(text, "in