rnba 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {rnba-0.2.0 → rnba-0.2.2}/PKG-INFO +12 -7
  2. {rnba-0.2.0 → rnba-0.2.2}/README.md +11 -6
  3. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/include/parser.hpp +10 -29
  4. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/src/main.cpp +5 -9
  5. rnba-0.2.2/cpp-sim/src/parser.cpp +366 -0
  6. rnba-0.2.2/cpp-sim/tests/test_parser.cpp +196 -0
  7. rnba-0.2.2/docs/analysis/schematic_inclusion_matrix.md +52 -0
  8. rnba-0.2.2/docs/bindings-file-format.md +61 -0
  9. rnba-0.2.2/docs/instruction-file-formats.md +218 -0
  10. rnba-0.2.2/docs/templates/schematic_template_manifest.md +44 -0
  11. {rnba-0.2.0 → rnba-0.2.2}/pyproject.toml +1 -1
  12. {rnba-0.2.0 → rnba-0.2.2}/rnba/__init__.py +11 -3
  13. {rnba-0.2.0 → rnba-0.2.2}/rnba/architecture.py +163 -115
  14. {rnba-0.2.0 → rnba-0.2.2}/rnba/simulation.py +10 -41
  15. {rnba-0.2.0 → rnba-0.2.2}/rnba/visualize_activity_flow.py +252 -42
  16. {rnba-0.2.0 → rnba-0.2.2}/tests/test_cpp_ncv_golden.py +16 -5
  17. rnba-0.2.2/tests/test_instruction_unified.py +196 -0
  18. rnba-0.2.2/tests/test_rnba_arch_converter.py +207 -0
  19. rnba-0.2.0/cpp-sim/src/parser.cpp +0 -426
  20. rnba-0.2.0/cpp-sim/tests/test_parser.cpp +0 -167
  21. rnba-0.2.0/docs/instruction-file-formats.md +0 -216
  22. rnba-0.2.0/docs/templates/schematic_template_manifest.md +0 -29
  23. {rnba-0.2.0 → rnba-0.2.2}/.gitignore +0 -0
  24. {rnba-0.2.0 → rnba-0.2.2}/LICENSE +0 -0
  25. {rnba-0.2.0 → rnba-0.2.2}/NBA24_QA_sentence_input.py +0 -0
  26. {rnba-0.2.0 → rnba-0.2.2}/NBA_SGall_sentence_input.py +0 -0
  27. {rnba-0.2.0 → rnba-0.2.2}/NBA_read_architecture.py +0 -0
  28. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/.gitignore +0 -0
  29. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/CMakeLists.txt +0 -0
  30. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/CMakePresets.json +0 -0
  31. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/README.md +0 -0
  32. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/README.md +0 -0
  33. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/example_snb1_contract.json +0 -0
  34. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/model_smoke_contract.json +0 -0
  35. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/network.schema.json +0 -0
  36. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/snb15_contract.json +0 -0
  37. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/data/snb20_contract.json +0 -0
  38. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/include/model.hpp +0 -0
  39. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/include/output.hpp +0 -0
  40. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/include/simulator.hpp +0 -0
  41. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/include/sparse_matrix.hpp +0 -0
  42. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/src/model.cpp +0 -0
  43. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/src/output.cpp +0 -0
  44. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/src/simulator.cpp +0 -0
  45. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/src/sparse_matrix.cpp +0 -0
  46. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tests/CMakeLists.txt +0 -0
  47. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tests/data/model_smoke_contract.json +0 -0
  48. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tests/data/snb20_contract.json +0 -0
  49. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tests/test_golden.cpp +0 -0
  50. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tests/test_simulator.cpp +0 -0
  51. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tools/export_network.py +0 -0
  52. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tools/plot_cpp_qa.py +0 -0
  53. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tools/plot_ncv_output.py +0 -0
  54. {rnba-0.2.0 → rnba-0.2.2}/cpp-sim/tools/plot_trace_comparison.py +0 -0
  55. {rnba-0.2.0 → rnba-0.2.2}/docs/analysis/QA_GENERATED_SIMULATION_WALKTHROUGH.md +0 -0
  56. {rnba-0.2.0 → rnba-0.2.2}/docs/analysis/README.md +0 -0
  57. {rnba-0.2.0 → rnba-0.2.2}/docs/analysis/collapse_verification.md +0 -0
  58. {rnba-0.2.0 → rnba-0.2.2}/docs/analysis/rowviz.py +0 -0
  59. {rnba-0.2.0 → rnba-0.2.2}/docs/analysis/visualize_core_circuits.py +0 -0
  60. {rnba-0.2.0 → rnba-0.2.2}/docs/snb15_population_categories.md +0 -0
  61. {rnba-0.2.0 → rnba-0.2.2}/examples/Collapsed_network_force.gexf +0 -0
  62. {rnba-0.2.0 → rnba-0.2.2}/examples/Collapsed_network_force_2026-05-22_noring_v1.gexf +0 -0
  63. {rnba-0.2.0 → rnba-0.2.2}/examples/Example_01_WIlsonCowan.ipynb +0 -0
  64. {rnba-0.2.0 → rnba-0.2.2}/examples/Example_02_BasicCircuits.ipynb +0 -0
  65. {rnba-0.2.0 → rnba-0.2.2}/examples/collapse_bgates.py +0 -0
  66. {rnba-0.2.0 → rnba-0.2.2}/examples/colour_gexf.py +0 -0
  67. {rnba-0.2.0 → rnba-0.2.2}/examples/demo_network_visualization.py +0 -0
  68. {rnba-0.2.0 → rnba-0.2.2}/examples/example_2.py +0 -0
  69. {rnba-0.2.0 → rnba-0.2.2}/examples/example_3.py +0 -0
  70. {rnba-0.2.0 → rnba-0.2.2}/examples/example_clamp.py +0 -0
  71. {rnba-0.2.0 → rnba-0.2.2}/examples/example_nCV_generated.py +0 -0
  72. {rnba-0.2.0 → rnba-0.2.2}/examples/example_network_creation.py +0 -0
  73. {rnba-0.2.0 → rnba-0.2.2}/examples/example_qa_create_network.py +0 -0
  74. {rnba-0.2.0 → rnba-0.2.2}/examples/example_qa_generated.py +0 -0
  75. {rnba-0.2.0 → rnba-0.2.2}/examples/example_qa_simulation.py +0 -0
  76. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_circle_16k.gexf +0 -0
  77. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_circle_ei_32k.gexf +0 -0
  78. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_ei_grey_32k.gexf +0 -0
  79. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_ei_grey_seeded_32k.gexf +0 -0
  80. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_force_16k.gexf +0 -0
  81. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_force_ei_32k.gexf +0 -0
  82. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_grey_16k.gexf +0 -0
  83. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_sfdp.gexf +0 -0
  84. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_sfdp_ei.gexf +0 -0
  85. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_sfdp_grey.gephi +0 -0
  86. {rnba-0.2.0 → rnba-0.2.2}/examples/full_network_sfdp_rows12.gexf +0 -0
  87. {rnba-0.2.0 → rnba-0.2.2}/examples/full_scale_sfdp_visualization.py +0 -0
  88. {rnba-0.2.0 → rnba-0.2.2}/examples/gexf_roles.py +0 -0
  89. {rnba-0.2.0 → rnba-0.2.2}/examples/layout_rows.py +0 -0
  90. {rnba-0.2.0 → rnba-0.2.2}/examples/render_collapsed.py +0 -0
  91. {rnba-0.2.0 → rnba-0.2.2}/examples/render_gexf.py +0 -0
  92. {rnba-0.2.0 → rnba-0.2.2}/examples/rnba_roles_row1.gexf +0 -0
  93. {rnba-0.2.0 → rnba-0.2.2}/examples/rnba_roles_snb15.gexf +0 -0
  94. {rnba-0.2.0 → rnba-0.2.2}/examples/row_visualization.py +0 -0
  95. {rnba-0.2.0 → rnba-0.2.2}/examples/seed_circle_ext.py +0 -0
  96. {rnba-0.2.0 → rnba-0.2.2}/examples/seed_ei_gexf.py +0 -0
  97. {rnba-0.2.0 → rnba-0.2.2}/examples/visualize_architecture.sh +0 -0
  98. {rnba-0.2.0 → rnba-0.2.2}/rnba/_sim.py +0 -0
  99. {rnba-0.2.0 → rnba-0.2.2}/rnba/blackboard.py +0 -0
  100. {rnba-0.2.0 → rnba-0.2.2}/rnba/circuit.py +0 -0
  101. {rnba-0.2.0 → rnba-0.2.2}/rnba/circuits/__init__.py +0 -0
  102. {rnba-0.2.0 → rnba-0.2.2}/rnba/circuits/core.py +0 -0
  103. {rnba-0.2.0 → rnba-0.2.2}/rnba/cli.py +0 -0
  104. {rnba-0.2.0 → rnba-0.2.2}/rnba/constants.py +0 -0
  105. {rnba-0.2.0 → rnba-0.2.2}/rnba/converters/__init__.py +0 -0
  106. {rnba-0.2.0 → rnba-0.2.2}/rnba/converters/architecture.py +0 -0
  107. {rnba-0.2.0 → rnba-0.2.2}/rnba/inputs.py +0 -0
  108. {rnba-0.2.0 → rnba-0.2.2}/rnba/network.py +0 -0
  109. {rnba-0.2.0 → rnba-0.2.2}/rnba/nodes.py +0 -0
  110. {rnba-0.2.0 → rnba-0.2.2}/rnba/plotting.py +0 -0
  111. {rnba-0.2.0 → rnba-0.2.2}/rnba/schematic_visualization.py +0 -0
  112. {rnba-0.2.0 → rnba-0.2.2}/rnba/visualize.py +0 -0
  113. {rnba-0.2.0 → rnba-0.2.2}/tests/QA_3NVN_O_fail.txt +0 -0
  114. {rnba-0.2.0 → rnba-0.2.2}/tests/QA_3NVN_O_success.txt +0 -0
  115. {rnba-0.2.0 → rnba-0.2.2}/tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt +0 -0
  116. {rnba-0.2.0 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB15.txt +0 -0
  117. {rnba-0.2.0 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB20.txt +0 -0
  118. {rnba-0.2.0 → rnba-0.2.2}/tests/RNBA_arch_SGall_SNB30.txt +0 -0
  119. {rnba-0.2.0 → rnba-0.2.2}/tests/Sentence_2CV.txt +0 -0
  120. {rnba-0.2.0 → rnba-0.2.2}/tests/conftest.py +0 -0
  121. {rnba-0.2.0 → rnba-0.2.2}/tests/integration/__init__.py +0 -0
  122. {rnba-0.2.0 → rnba-0.2.2}/tests/integration/test_architecture_vs_gexf.py +0 -0
  123. {rnba-0.2.0 → rnba-0.2.2}/tests/test_architecture.py +0 -0
  124. {rnba-0.2.0 → rnba-0.2.2}/tests/test_basic.py +0 -0
  125. {rnba-0.2.0 → rnba-0.2.2}/tests/test_blackboard.py +0 -0
  126. {rnba-0.2.0 → rnba-0.2.2}/tests/test_circuit.py +0 -0
  127. {rnba-0.2.0 → rnba-0.2.2}/tests/test_circuits.py +0 -0
  128. {rnba-0.2.0 → rnba-0.2.2}/tests/test_clamp_integration.py +0 -0
  129. {rnba-0.2.0 → rnba-0.2.2}/tests/test_instruction_window_semantics.py +0 -0
  130. {rnba-0.2.0 → rnba-0.2.2}/tests/test_network.py +0 -0
  131. {rnba-0.2.0 → rnba-0.2.2}/tests/test_nodes.py +0 -0
  132. {rnba-0.2.0 → rnba-0.2.2}/tests/test_rnba_simulation_launcher.py +0 -0
  133. {rnba-0.2.0 → rnba-0.2.2}/tests/test_simulation_engines.py +0 -0
  134. {rnba-0.2.0 → rnba-0.2.2}/tests/test_visualize.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rnba
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Recurrent Neural Blackboard Architecture - a simulation framework for neuro-cognitive modelling.
5
5
  Author-Email: RNBA Contributors <m.dekamps@leeds.ac.uk>
6
6
  License: MIT
@@ -74,8 +74,8 @@ Plotting and visualization extras: `pip install "rnba[viz]"` (matplotlib + graph
74
74
  This is the typical use of the package. A simulation needs:
75
75
 
76
76
  1. an **architecture file** describing the complete neural blackboard (e.g. `tests/RNBA_arch_SGall_SNB15.txt`);
77
- 2. an **instruction file** giving the timings at which populations are switched on and off — word inputs, gating populations, etc. Both instruction formats (QA and SGall) are accepted and auto-detected; they are fully documented in [docs/instruction-file-formats.md](docs/instruction-file-formats.md);
78
- 3. optionally a **bindings file**, which pre-stores bindings in the network, emulating sentences that were read into the blackboard previously (e.g. `tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt`).
77
+ 2. an **instruction file** giving the timings at which populations are switched on and off — word inputs, gating populations, etc. Both simulators read the unified instruction format (legacy QA and SGall files are valid subsets) with strict parsing; it is fully documented in [docs/instruction-file-formats.md](docs/instruction-file-formats.md);
78
+ 3. optionally a **bindings file**, which pre-stores bindings in the network, emulating sentences that were read into the blackboard previously (e.g. `tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt`); the format is documented in [docs/bindings-file-format.md](docs/bindings-file-format.md).
79
79
 
80
80
  The output is a CSV with one row per timestep (`h = 0.1` ms) and one column per population.
81
81
 
@@ -161,6 +161,10 @@ python -m rnba.visualize_activity_flow \
161
161
  --fps 25 --step-ms 1.0
162
162
  ```
163
163
 
164
+ To zoom in on the top of the blackboard, add `--first-rows N` (e.g. `--first-rows 5`): the view is cropped to the first N rows and markers are enlarged for legibility, while the layout and trace mapping stay identical to the full 15-row view. This works for both the single-frame preview (`--preview-time-ms`) and full video rendering, and mirrors the `--first-rows` option of the static schematic renderer (see below).
165
+
166
+ Frame rendering dominates the runtime. Pass `--workers N` to render frames across N processes (each holds its own copy of the base schematic); output is identical to a sequential run. `--dpi` trades resolution for size but has little effect on speed, since most per-frame cost is drawing the schematic rather than rasterising pixels.
167
+
164
168
  See `python -m rnba.visualize_activity_flow --help` for the full option set (codec, styling, frame selection).
165
169
 
166
170
  ## Quick Start: Replicating the QA Simulation
@@ -222,10 +226,11 @@ RNBA uses **two complementary naming schemes**:
222
226
  ## Documentation
223
227
 
224
228
  Project documentation lives in the docs/ folder:
225
- - Instruction file formats (QA and SGall): docs/instruction-file-formats.md
226
- - Analysis: docs/analysis/
227
- - Design notes: docs/design/
228
- - Circuit library design and implementation status: docs/design/CIRCUIT_LIBRARY_DESIGN.md
229
+ - Instruction file format (unified; legacy QA and SGall dialects): [docs/instruction-file-formats.md](docs/instruction-file-formats.md)
230
+ - Bindings file format: [docs/bindings-file-format.md](docs/bindings-file-format.md)
231
+ - Analysis: [docs/analysis/](docs/analysis/)
232
+ - Design notes: [docs/design/](docs/design/)
233
+ - Circuit library design and implementation status: [docs/design/CIRCUIT_LIBRARY_DESIGN.md](docs/design/CIRCUIT_LIBRARY_DESIGN.md)
229
234
 
230
235
  ### Circuit Factory Functions
231
236
 
@@ -37,8 +37,8 @@ Plotting and visualization extras: `pip install "rnba[viz]"` (matplotlib + graph
37
37
  This is the typical use of the package. A simulation needs:
38
38
 
39
39
  1. an **architecture file** describing the complete neural blackboard (e.g. `tests/RNBA_arch_SGall_SNB15.txt`);
40
- 2. an **instruction file** giving the timings at which populations are switched on and off — word inputs, gating populations, etc. Both instruction formats (QA and SGall) are accepted and auto-detected; they are fully documented in [docs/instruction-file-formats.md](docs/instruction-file-formats.md);
41
- 3. optionally a **bindings file**, which pre-stores bindings in the network, emulating sentences that were read into the blackboard previously (e.g. `tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt`).
40
+ 2. an **instruction file** giving the timings at which populations are switched on and off — word inputs, gating populations, etc. Both simulators read the unified instruction format (legacy QA and SGall files are valid subsets) with strict parsing; it is fully documented in [docs/instruction-file-formats.md](docs/instruction-file-formats.md);
41
+ 3. optionally a **bindings file**, which pre-stores bindings in the network, emulating sentences that were read into the blackboard previously (e.g. `tests/RNBA_SGall_SNB15_Bindings_Sentence_3NVN.txt`); the format is documented in [docs/bindings-file-format.md](docs/bindings-file-format.md).
42
42
 
43
43
  The output is a CSV with one row per timestep (`h = 0.1` ms) and one column per population.
44
44
 
@@ -124,6 +124,10 @@ python -m rnba.visualize_activity_flow \
124
124
  --fps 25 --step-ms 1.0
125
125
  ```
126
126
 
127
+ To zoom in on the top of the blackboard, add `--first-rows N` (e.g. `--first-rows 5`): the view is cropped to the first N rows and markers are enlarged for legibility, while the layout and trace mapping stay identical to the full 15-row view. This works for both the single-frame preview (`--preview-time-ms`) and full video rendering, and mirrors the `--first-rows` option of the static schematic renderer (see below).
128
+
129
+ Frame rendering dominates the runtime. Pass `--workers N` to render frames across N processes (each holds its own copy of the base schematic); output is identical to a sequential run. `--dpi` trades resolution for size but has little effect on speed, since most per-frame cost is drawing the schematic rather than rasterising pixels.
130
+
127
131
  See `python -m rnba.visualize_activity_flow --help` for the full option set (codec, styling, frame selection).
128
132
 
129
133
  ## Quick Start: Replicating the QA Simulation
@@ -185,10 +189,11 @@ RNBA uses **two complementary naming schemes**:
185
189
  ## Documentation
186
190
 
187
191
  Project documentation lives in the docs/ folder:
188
- - Instruction file formats (QA and SGall): docs/instruction-file-formats.md
189
- - Analysis: docs/analysis/
190
- - Design notes: docs/design/
191
- - Circuit library design and implementation status: docs/design/CIRCUIT_LIBRARY_DESIGN.md
192
+ - Instruction file format (unified; legacy QA and SGall dialects): [docs/instruction-file-formats.md](docs/instruction-file-formats.md)
193
+ - Bindings file format: [docs/bindings-file-format.md](docs/bindings-file-format.md)
194
+ - Analysis: [docs/analysis/](docs/analysis/)
195
+ - Design notes: [docs/design/](docs/design/)
196
+ - Circuit library design and implementation status: [docs/design/CIRCUIT_LIBRARY_DESIGN.md](docs/design/CIRCUIT_LIBRARY_DESIGN.md)
192
197
 
193
198
  ### Circuit Factory Functions
194
199
 
@@ -17,42 +17,23 @@ struct ScheduleWindow {
17
17
  double amplitude;
18
18
  };
19
19
 
20
- enum class InstructionFormat {
21
- SGall,
22
- QA,
23
- };
24
-
25
- struct ParsedInstructionSchedule {
26
- InstructionFormat format;
27
- std::map<std::string, std::vector<ScheduleWindow>> schedule;
28
- };
29
-
30
20
  /**
31
- * Parse instruction file (e.g., QA_3NVN_O_success.txt).
21
+ * Parse an instruction file in the unified format (see
22
+ * docs/instruction-file-formats.md). Legacy SGall and QA files are
23
+ * valid subsets and parse unchanged.
24
+ *
25
+ * Parsing is strict: unknown commands, wrong arities, out-of-range word
26
+ * indices, misplaced directives and degenerate windows all throw
27
+ * std::runtime_error with the offending file/line.
28
+ *
29
+ * Times in the file are integer timestep indices (multiply by h=0.1 ms).
32
30
  * Returns map from pop_id → list of schedule windows.
33
- *
34
- * File format (line-oriented):
35
- * t_start t_end pop_id
36
- * ...
37
- * where times are in timestep indices (multiply by h=0.1 ms).
38
31
  */
39
32
  std::map<std::string, std::vector<ScheduleWindow>>
40
33
  parse_instruction_file(
41
34
  const std::string& filepath,
42
35
  double h = 0.1,
43
- double amplitude = 100.0,
44
- bool strict = false
45
- );
46
-
47
- /**
48
- * Parse instruction file and return both detected format and schedule.
49
- */
50
- ParsedInstructionSchedule
51
- parse_instruction_file_with_format(
52
- const std::string& filepath,
53
- double h = 0.1,
54
- double amplitude = 100.0,
55
- bool strict = false
36
+ double amplitude = 100.0
56
37
  );
57
38
 
58
39
  /**
@@ -30,7 +30,8 @@ void print_usage(const char* prog) {
30
30
  << " --t-end N*0.1. Mutually exclusive with --t-end.\n\n"
31
31
  << "Optional arguments:\n"
32
32
  << " --instructions FILE\n"
33
- << " QA instruction file in the existing RNBA text format. If provided,\n"
33
+ << " Instruction file in the unified RNBA text format (legacy QA and\n"
34
+ << " SGall files are valid subsets; parsed strictly). If provided,\n"
34
35
  << " instruction windows are mapped onto model input populations.\n"
35
36
  << " --bindings FILE\n"
36
37
  << " WM bindings file in the existing RNBA text format. If provided,\n"
@@ -168,18 +169,13 @@ int main(int argc, char* argv[]) {
168
169
  // Parse and apply instruction schedule
169
170
  if (!instr_file.empty()) {
170
171
  std::cout << "Parsing instruction file: " << instr_file << "\n";
171
- auto parsed = rnba::parse_instruction_file_with_format(instr_file, 0.1, 100.0, true);
172
-
173
- if (parsed.format == rnba::InstructionFormat::SGall) {
174
- std::cout << " Detected instruction format: SGall (drive)\n";
175
- } else {
176
- std::cout << " Detected instruction format: QA (drive)\n";
177
- }
172
+ std::cout << " Instruction format: unified (strict parsing)\n";
173
+ const auto schedule = rnba::parse_instruction_file(instr_file, 0.1, 100.0);
178
174
 
179
175
  int applied = 0;
180
176
  int missing = 0;
181
177
 
182
- for (const auto& kv : parsed.schedule) {
178
+ for (const auto& kv : schedule) {
183
179
  const auto it = input_name_to_idx.find(kv.first);
184
180
  if (it == input_name_to_idx.end()) {
185
181
  missing += 1;
@@ -0,0 +1,366 @@
1
+ #include "parser.hpp"
2
+ #include <algorithm>
3
+ #include <fstream>
4
+ #include <iostream>
5
+ #include <map>
6
+ #include <sstream>
7
+ #include <stdexcept>
8
+ #include <string>
9
+ #include <unordered_map>
10
+ #include <vector>
11
+
12
+ namespace rnba {
13
+
14
+ namespace {
15
+
16
+ std::vector<std::string> split_ws(const std::string& s) {
17
+ std::istringstream iss(s);
18
+ std::vector<std::string> out;
19
+ std::string tok;
20
+ while (iss >> tok) {
21
+ out.push_back(tok);
22
+ }
23
+ return out;
24
+ }
25
+
26
+ std::unordered_map<std::string, std::string> build_command_map() {
27
+ std::unordered_map<std::string, std::string> m;
28
+ m["Word_inhib"] = "EX0a_inhib_W_all";
29
+
30
+ const std::vector<std::string> lma_short = {
31
+ "Adj", "Adv", "C", "Co", "Det", "N", "P", "Pr", "V"
32
+ };
33
+ const std::vector<std::string> lma_full = {
34
+ "Adjective", "Adverb", "Clause", "Coordinator", "Determiner",
35
+ "Noun", "Preposition", "Pronoun", "Verb"
36
+ };
37
+ for (size_t i = 0; i < lma_short.size(); ++i) {
38
+ const std::string& sh = lma_short[i];
39
+ const std::string& fu = lma_full[i];
40
+ m[fu] = "EX2_Bgate_W_" + sh;
41
+ m[fu + "_H"] = "EX3H_Gate_" + sh + "_H";
42
+ m[fu + "_D"] = "EX3D_Gate_" + sh + "_D";
43
+ m["CMtoW_" + fu + "_H"] = "EX8H_LMA_out_" + sh + "_H";
44
+ m["CMtoW_" + fu + "_D"] = "EX8D_LMA_out_" + sh + "_D";
45
+ m[fu + "_inhib"] = "EX0_inhib_" + sh;
46
+ }
47
+
48
+ const std::vector<std::string> sg_names = {
49
+ "SG1Verb", "SG2Subject", "SG3Object", "SG4Det", "SG5Compl",
50
+ "SG6Mod", "SG7Adjunct", "SG8PC", "SG9Ex_Mod", "SG10Coord", "SG11GAP"
51
+ };
52
+ for (const auto& sg : sg_names) {
53
+ m["binding_" + sg + "_H"] = "EX4H_Bgate_LMA_" + sg + "_H";
54
+ m["binding_" + sg + "_D"] = "EX4D_Bgate_LMA_" + sg + "_D";
55
+ m[sg + "_H"] = "EX6H_Gate_" + sg + "_CM_H";
56
+ m[sg + "_D"] = "EX6D_Gate_" + sg + "_CM_D";
57
+ m[sg + "_W_H"] = "EX5H_Gate_" + sg + "_W_H";
58
+ m[sg + "_W_D"] = "EX5D_Gate_" + sg + "_W_D";
59
+ m["inhibcomp_" + sg + "_H"] = "EX7H_SG_comp_inhib_" + sg + "_H";
60
+ m["inhibcomp_" + sg + "_D"] = "EX7D_SG_comp_inhib_" + sg + "_D";
61
+ }
62
+
63
+ return m;
64
+ }
65
+
66
+ const std::unordered_map<std::string, std::string> kCommandMap = build_command_map();
67
+
68
+ constexpr const char* kFormatDirectiveName = "rnba-instructions";
69
+ constexpr int kFormatDirectiveVersion = 1;
70
+
71
+ [[noreturn]] void parse_error(
72
+ const std::string& filepath,
73
+ int line_no,
74
+ const std::string& message
75
+ ) {
76
+ throw std::runtime_error(
77
+ filepath + ":" + std::to_string(line_no) + ": " + message
78
+ );
79
+ }
80
+
81
+ int parse_int(
82
+ const std::string& token,
83
+ const std::string& filepath,
84
+ int line_no,
85
+ const std::string& what
86
+ ) {
87
+ try {
88
+ size_t consumed = 0;
89
+ const int value = std::stoi(token, &consumed);
90
+ if (consumed != token.size()) {
91
+ parse_error(filepath, line_no,
92
+ what + " must be an integer, got '" + token + "'");
93
+ }
94
+ return value;
95
+ } catch (const std::invalid_argument&) {
96
+ parse_error(filepath, line_no,
97
+ what + " must be an integer, got '" + token + "'");
98
+ } catch (const std::out_of_range&) {
99
+ parse_error(filepath, line_no,
100
+ what + " out of integer range: '" + token + "'");
101
+ }
102
+ }
103
+
104
+ } // namespace
105
+
106
+ // ─── Public Parser Interface ─────────────────────────────────────────────────
107
+ // Unified strict instruction parser (see docs/instruction-file-formats.md).
108
+ // Legacy SGall and QA files are valid subsets and parse unchanged; word
109
+ // windows without an explicit end take their length from fixed_time.
110
+ std::map<std::string, std::vector<ScheduleWindow>>
111
+ parse_instruction_file(
112
+ const std::string& filepath,
113
+ double h,
114
+ double amplitude
115
+ ) {
116
+ std::map<std::string, std::vector<ScheduleWindow>> result;
117
+
118
+ std::ifstream in(filepath);
119
+ if (!in) {
120
+ throw std::runtime_error("Failed to open instruction file: " + filepath);
121
+ }
122
+
123
+ bool have_word_list = false;
124
+ std::vector<std::string> word_list;
125
+ bool have_fixed_time = false;
126
+ int fixed_time = 0;
127
+ int step_size = 0;
128
+ bool seen_command = false;
129
+ bool seen_format = false;
130
+ bool seen_fixed_time = false;
131
+ bool seen_num_of_words = false;
132
+
133
+ std::string raw_line;
134
+ int line_no = 0;
135
+ while (std::getline(in, raw_line)) {
136
+ line_no += 1;
137
+ const auto parts = split_ws(raw_line);
138
+ if (parts.empty()) {
139
+ continue;
140
+ }
141
+ // Comments occupy whole lines; leading whitespace is allowed.
142
+ if (!parts[0].empty() && parts[0][0] == '#') {
143
+ continue;
144
+ }
145
+
146
+ const std::string& cmd = parts[0];
147
+
148
+ if (cmd == "format" || cmd == "fixed_time" || cmd == "Num_of_words") {
149
+ if (seen_command) {
150
+ parse_error(filepath, line_no,
151
+ "directive '" + cmd +
152
+ "' must appear before the first command");
153
+ }
154
+ bool& seen_flag = (cmd == "format") ? seen_format
155
+ : (cmd == "fixed_time") ? seen_fixed_time
156
+ : seen_num_of_words;
157
+ if (seen_flag) {
158
+ parse_error(filepath, line_no, "duplicate directive '" + cmd + "'");
159
+ }
160
+ seen_flag = true;
161
+
162
+ if (cmd == "format") {
163
+ if (parts.size() != 3 || parts[1] != kFormatDirectiveName) {
164
+ parse_error(filepath, line_no,
165
+ std::string("format directive must read 'format ") +
166
+ kFormatDirectiveName + " " +
167
+ std::to_string(kFormatDirectiveVersion) + "'");
168
+ }
169
+ const int version = parse_int(parts[2], filepath, line_no, "format version");
170
+ if (version != kFormatDirectiveVersion) {
171
+ parse_error(filepath, line_no,
172
+ "unsupported format version " + std::to_string(version) +
173
+ " (supported: " + std::to_string(kFormatDirectiveVersion) + ")");
174
+ }
175
+ } else if (cmd == "fixed_time") {
176
+ if (parts.size() != 2) {
177
+ parse_error(filepath, line_no, "fixed_time takes exactly one value");
178
+ }
179
+ fixed_time = parse_int(parts[1], filepath, line_no, "fixed_time");
180
+ if (fixed_time <= 0) {
181
+ parse_error(filepath, line_no, "fixed_time must be positive");
182
+ }
183
+ have_fixed_time = true;
184
+ } else { // Num_of_words
185
+ if (parts.size() != 2) {
186
+ parse_error(filepath, line_no, "Num_of_words takes exactly one value");
187
+ }
188
+ const int n_words = parse_int(parts[1], filepath, line_no, "Num_of_words");
189
+ if (n_words < 0) {
190
+ parse_error(filepath, line_no, "Num_of_words must be non-negative");
191
+ }
192
+ word_list.clear();
193
+ word_list.reserve(static_cast<size_t>(n_words));
194
+ for (int k = 0; k < n_words; ++k) {
195
+ word_list.push_back("EX1_input_W" + std::to_string(k));
196
+ }
197
+ have_word_list = true;
198
+ }
199
+ continue;
200
+ }
201
+
202
+ seen_command = true;
203
+
204
+ int start = 0;
205
+ int end = 0;
206
+ std::string pop_id;
207
+
208
+ if (cmd == "Word") {
209
+ if (!have_word_list) {
210
+ parse_error(filepath, line_no, "'Word' encountered before Num_of_words");
211
+ }
212
+ int word_idx = 0;
213
+ if (parts.size() == 3) {
214
+ if (!have_fixed_time) {
215
+ parse_error(filepath, line_no,
216
+ "2-argument 'Word' requires a fixed_time directive; "
217
+ "use 'Word <rel_start> <rel_end> <word_idx>' for an explicit end");
218
+ }
219
+ const int rel_start = parse_int(parts[1], filepath, line_no, "rel_start");
220
+ word_idx = parse_int(parts[2], filepath, line_no, "word index");
221
+ start = step_size + rel_start;
222
+ step_size = start;
223
+ end = start + fixed_time;
224
+ } else if (parts.size() == 4) {
225
+ const int rel_start = parse_int(parts[1], filepath, line_no, "rel_start");
226
+ const int rel_end = parse_int(parts[2], filepath, line_no, "rel_end");
227
+ word_idx = parse_int(parts[3], filepath, line_no, "word index");
228
+ start = step_size + rel_start;
229
+ step_size = start;
230
+ end = start + rel_end;
231
+ } else {
232
+ parse_error(filepath, line_no,
233
+ "'Word' takes 2 arguments (rel_start word_idx, with fixed_time) "
234
+ "or 3 (rel_start rel_end word_idx)");
235
+ }
236
+ if (word_idx < 0 || word_idx >= static_cast<int>(word_list.size())) {
237
+ parse_error(filepath, line_no,
238
+ "word index " + std::to_string(word_idx) +
239
+ " out of range (Num_of_words = " +
240
+ std::to_string(word_list.size()) + ")");
241
+ }
242
+ pop_id = word_list[static_cast<size_t>(word_idx)];
243
+ } else {
244
+ const auto it = kCommandMap.find(cmd);
245
+ if (it == kCommandMap.end()) {
246
+ parse_error(filepath, line_no, "unknown command '" + cmd + "'");
247
+ }
248
+ if (parts.size() == 2) {
249
+ if (!have_fixed_time) {
250
+ parse_error(filepath, line_no,
251
+ "1-argument '" + cmd + "' requires a fixed_time directive; "
252
+ "use '" + cmd + " <rel_start> <rel_end>' for an explicit end");
253
+ }
254
+ start = step_size + parse_int(parts[1], filepath, line_no, "rel_start");
255
+ end = start + fixed_time;
256
+ } else if (parts.size() == 3) {
257
+ start = step_size + parse_int(parts[1], filepath, line_no, "rel_start");
258
+ end = step_size + parse_int(parts[2], filepath, line_no, "rel_end");
259
+ } else {
260
+ parse_error(filepath, line_no,
261
+ "'" + cmd + "' takes 1 argument (rel_start, with fixed_time) "
262
+ "or 2 (rel_start rel_end)");
263
+ }
264
+ pop_id = it->second;
265
+ }
266
+
267
+ if (start < 0) {
268
+ parse_error(filepath, line_no,
269
+ "window start " + std::to_string(start) + " is negative");
270
+ }
271
+ if (end <= start) {
272
+ parse_error(filepath, line_no,
273
+ "window end " + std::to_string(end) +
274
+ " does not lie after start " + std::to_string(start));
275
+ }
276
+ result[pop_id].push_back(ScheduleWindow{start * h, end * h, amplitude});
277
+ }
278
+
279
+ return result;
280
+ }
281
+
282
+ std::vector<int>
283
+ parse_bindings_file(const std::string& filepath) {
284
+ std::vector<int> result;
285
+
286
+ std::ifstream in(filepath);
287
+ if (!in) {
288
+ throw std::runtime_error("Failed to open bindings file: " + filepath);
289
+ }
290
+
291
+ std::string raw_line;
292
+ while (std::getline(in, raw_line)) {
293
+ const auto parts = split_ws(raw_line);
294
+ if (parts.empty()) {
295
+ continue;
296
+ }
297
+ // Skip comments and non-data headings.
298
+ if (!parts[0].empty() && parts[0][0] == '#') {
299
+ continue;
300
+ }
301
+
302
+ // Data lines begin with p_index as first integer token.
303
+ // Headings like "p_index ..." are skipped by failed stoi.
304
+ try {
305
+ const int idx = std::stoi(parts[0]);
306
+ if (idx < 0) {
307
+ throw std::runtime_error("Negative binding index encountered: " + std::to_string(idx));
308
+ }
309
+ result.push_back(idx);
310
+ } catch (const std::invalid_argument&) {
311
+ // Ignore non-numeric first token lines.
312
+ continue;
313
+ } catch (const std::out_of_range&) {
314
+ throw std::runtime_error("Binding index out of range in line: " + raw_line);
315
+ }
316
+ }
317
+
318
+ return result;
319
+ }
320
+
321
+ std::function<double(double)>
322
+ make_piecewise_activation(const std::vector<ScheduleWindow>& windows) {
323
+ return [windows](double t) -> double {
324
+ for (const auto& win : windows) {
325
+ if (t >= win.t_start && t <= win.t_end) {
326
+ return win.amplitude;
327
+ }
328
+ }
329
+ return 0.0;
330
+ };
331
+ }
332
+
333
+ std::function<double(double)>
334
+ make_piecewise_activation_legacy_window(
335
+ const std::vector<ScheduleWindow>& windows,
336
+ double h
337
+ ) {
338
+ const double eps = std::max(1e-12, 0.1 * h);
339
+ return [windows, eps](double t) -> double {
340
+ for (const auto& win : windows) {
341
+ if ((t - win.t_start) > eps && (win.t_end - t) >= -eps) {
342
+ return win.amplitude;
343
+ }
344
+ }
345
+ return 0.0;
346
+ };
347
+ }
348
+
349
+ std::function<double(double)>
350
+ make_clamp_function(
351
+ const std::vector<int>& clamped_nodes,
352
+ double t_start,
353
+ double t_end,
354
+ double value
355
+ ) {
356
+ (void)clamped_nodes;
357
+ const double eps = 1e-12;
358
+ return [t_start, t_end, value, eps](double t) -> double {
359
+ if (t + eps >= t_start && t <= t_end + eps) {
360
+ return value;
361
+ }
362
+ return 0.0;
363
+ };
364
+ }
365
+
366
+ } // namespace rnba