sierra-research 1.3.6__py3-none-any.whl → 1.5.0__py3-none-any.whl

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 (254) hide show
  1. sierra/__init__.py +3 -3
  2. sierra/core/__init__.py +3 -3
  3. sierra/core/batchroot.py +223 -0
  4. sierra/core/cmdline.py +681 -1057
  5. sierra/core/compare.py +11 -0
  6. sierra/core/config.py +96 -88
  7. sierra/core/engine.py +306 -0
  8. sierra/core/execenv.py +380 -0
  9. sierra/core/expdef.py +11 -0
  10. sierra/core/experiment/__init__.py +1 -0
  11. sierra/core/experiment/bindings.py +150 -101
  12. sierra/core/experiment/definition.py +414 -245
  13. sierra/core/experiment/spec.py +83 -85
  14. sierra/core/exproot.py +44 -0
  15. sierra/core/generators/__init__.py +10 -0
  16. sierra/core/generators/experiment.py +528 -0
  17. sierra/core/generators/generator_factory.py +138 -137
  18. sierra/core/graphs/__init__.py +23 -0
  19. sierra/core/graphs/bcbridge.py +94 -0
  20. sierra/core/graphs/heatmap.py +245 -324
  21. sierra/core/graphs/pathset.py +27 -0
  22. sierra/core/graphs/schema.py +77 -0
  23. sierra/core/graphs/stacked_line.py +341 -0
  24. sierra/core/graphs/summary_line.py +506 -0
  25. sierra/core/logging.py +3 -2
  26. sierra/core/models/__init__.py +3 -1
  27. sierra/core/models/info.py +19 -0
  28. sierra/core/models/interface.py +52 -122
  29. sierra/core/pipeline/__init__.py +2 -5
  30. sierra/core/pipeline/pipeline.py +228 -126
  31. sierra/core/pipeline/stage1/__init__.py +10 -0
  32. sierra/core/pipeline/stage1/pipeline_stage1.py +45 -31
  33. sierra/core/pipeline/stage2/__init__.py +10 -0
  34. sierra/core/pipeline/stage2/pipeline_stage2.py +8 -11
  35. sierra/core/pipeline/stage2/runner.py +401 -0
  36. sierra/core/pipeline/stage3/__init__.py +12 -0
  37. sierra/core/pipeline/stage3/gather.py +321 -0
  38. sierra/core/pipeline/stage3/pipeline_stage3.py +37 -84
  39. sierra/core/pipeline/stage4/__init__.py +12 -2
  40. sierra/core/pipeline/stage4/pipeline_stage4.py +36 -354
  41. sierra/core/pipeline/stage5/__init__.py +12 -0
  42. sierra/core/pipeline/stage5/pipeline_stage5.py +33 -208
  43. sierra/core/pipeline/yaml.py +48 -0
  44. sierra/core/plugin.py +529 -62
  45. sierra/core/proc.py +11 -0
  46. sierra/core/prod.py +11 -0
  47. sierra/core/ros1/__init__.py +5 -1
  48. sierra/core/ros1/callbacks.py +22 -21
  49. sierra/core/ros1/cmdline.py +59 -88
  50. sierra/core/ros1/generators.py +159 -175
  51. sierra/core/ros1/variables/__init__.py +3 -0
  52. sierra/core/ros1/variables/exp_setup.py +122 -116
  53. sierra/core/startup.py +106 -76
  54. sierra/core/stat_kernels.py +4 -5
  55. sierra/core/storage.py +13 -32
  56. sierra/core/trampoline.py +30 -0
  57. sierra/core/types.py +116 -71
  58. sierra/core/utils.py +103 -106
  59. sierra/core/variables/__init__.py +1 -1
  60. sierra/core/variables/base_variable.py +12 -17
  61. sierra/core/variables/batch_criteria.py +387 -481
  62. sierra/core/variables/builtin.py +135 -0
  63. sierra/core/variables/exp_setup.py +19 -39
  64. sierra/core/variables/population_size.py +72 -76
  65. sierra/core/variables/variable_density.py +44 -68
  66. sierra/core/vector.py +1 -1
  67. sierra/main.py +256 -88
  68. sierra/plugins/__init__.py +119 -0
  69. sierra/plugins/compare/__init__.py +14 -0
  70. sierra/plugins/compare/graphs/__init__.py +19 -0
  71. sierra/plugins/compare/graphs/cmdline.py +120 -0
  72. sierra/plugins/compare/graphs/comparator.py +291 -0
  73. sierra/plugins/compare/graphs/inter_controller.py +531 -0
  74. sierra/plugins/compare/graphs/inter_scenario.py +297 -0
  75. sierra/plugins/compare/graphs/namecalc.py +53 -0
  76. sierra/plugins/compare/graphs/outputroot.py +73 -0
  77. sierra/plugins/compare/graphs/plugin.py +147 -0
  78. sierra/plugins/compare/graphs/preprocess.py +172 -0
  79. sierra/plugins/compare/graphs/schema.py +37 -0
  80. sierra/plugins/engine/__init__.py +14 -0
  81. sierra/plugins/engine/argos/__init__.py +18 -0
  82. sierra/plugins/{platform → engine}/argos/cmdline.py +144 -151
  83. sierra/plugins/{platform/argos/variables → engine/argos/generators}/__init__.py +5 -0
  84. sierra/plugins/engine/argos/generators/engine.py +394 -0
  85. sierra/plugins/engine/argos/plugin.py +393 -0
  86. sierra/plugins/{platform/argos/generators → engine/argos/variables}/__init__.py +5 -0
  87. sierra/plugins/engine/argos/variables/arena_shape.py +183 -0
  88. sierra/plugins/engine/argos/variables/cameras.py +240 -0
  89. sierra/plugins/engine/argos/variables/constant_density.py +112 -0
  90. sierra/plugins/engine/argos/variables/exp_setup.py +82 -0
  91. sierra/plugins/{platform → engine}/argos/variables/physics_engines.py +83 -87
  92. sierra/plugins/engine/argos/variables/population_constant_density.py +178 -0
  93. sierra/plugins/engine/argos/variables/population_size.py +115 -0
  94. sierra/plugins/engine/argos/variables/population_variable_density.py +123 -0
  95. sierra/plugins/engine/argos/variables/rendering.py +108 -0
  96. sierra/plugins/engine/ros1gazebo/__init__.py +18 -0
  97. sierra/plugins/engine/ros1gazebo/cmdline.py +175 -0
  98. sierra/plugins/{platform/ros1robot → engine/ros1gazebo}/generators/__init__.py +5 -0
  99. sierra/plugins/engine/ros1gazebo/generators/engine.py +125 -0
  100. sierra/plugins/engine/ros1gazebo/plugin.py +404 -0
  101. sierra/plugins/engine/ros1gazebo/variables/__init__.py +15 -0
  102. sierra/plugins/engine/ros1gazebo/variables/population_size.py +214 -0
  103. sierra/plugins/engine/ros1robot/__init__.py +18 -0
  104. sierra/plugins/engine/ros1robot/cmdline.py +159 -0
  105. sierra/plugins/{platform/ros1gazebo → engine/ros1robot}/generators/__init__.py +4 -0
  106. sierra/plugins/engine/ros1robot/generators/engine.py +95 -0
  107. sierra/plugins/engine/ros1robot/plugin.py +410 -0
  108. sierra/plugins/{hpc/local → engine/ros1robot/variables}/__init__.py +5 -0
  109. sierra/plugins/engine/ros1robot/variables/population_size.py +146 -0
  110. sierra/plugins/execenv/__init__.py +11 -0
  111. sierra/plugins/execenv/hpc/__init__.py +18 -0
  112. sierra/plugins/execenv/hpc/adhoc/__init__.py +18 -0
  113. sierra/plugins/execenv/hpc/adhoc/cmdline.py +30 -0
  114. sierra/plugins/execenv/hpc/adhoc/plugin.py +131 -0
  115. sierra/plugins/execenv/hpc/cmdline.py +137 -0
  116. sierra/plugins/execenv/hpc/local/__init__.py +18 -0
  117. sierra/plugins/execenv/hpc/local/cmdline.py +31 -0
  118. sierra/plugins/execenv/hpc/local/plugin.py +145 -0
  119. sierra/plugins/execenv/hpc/pbs/__init__.py +18 -0
  120. sierra/plugins/execenv/hpc/pbs/cmdline.py +30 -0
  121. sierra/plugins/execenv/hpc/pbs/plugin.py +121 -0
  122. sierra/plugins/execenv/hpc/slurm/__init__.py +18 -0
  123. sierra/plugins/execenv/hpc/slurm/cmdline.py +30 -0
  124. sierra/plugins/execenv/hpc/slurm/plugin.py +133 -0
  125. sierra/plugins/execenv/prefectserver/__init__.py +18 -0
  126. sierra/plugins/execenv/prefectserver/cmdline.py +66 -0
  127. sierra/plugins/execenv/prefectserver/dockerremote/__init__.py +18 -0
  128. sierra/plugins/execenv/prefectserver/dockerremote/cmdline.py +66 -0
  129. sierra/plugins/execenv/prefectserver/dockerremote/plugin.py +132 -0
  130. sierra/plugins/execenv/prefectserver/flow.py +66 -0
  131. sierra/plugins/execenv/prefectserver/local/__init__.py +18 -0
  132. sierra/plugins/execenv/prefectserver/local/cmdline.py +29 -0
  133. sierra/plugins/execenv/prefectserver/local/plugin.py +133 -0
  134. sierra/plugins/{hpc/adhoc → execenv/robot}/__init__.py +1 -0
  135. sierra/plugins/execenv/robot/turtlebot3/__init__.py +18 -0
  136. sierra/plugins/execenv/robot/turtlebot3/plugin.py +204 -0
  137. sierra/plugins/expdef/__init__.py +14 -0
  138. sierra/plugins/expdef/json/__init__.py +14 -0
  139. sierra/plugins/expdef/json/plugin.py +504 -0
  140. sierra/plugins/expdef/xml/__init__.py +14 -0
  141. sierra/plugins/expdef/xml/plugin.py +386 -0
  142. sierra/{core/hpc → plugins/proc}/__init__.py +1 -1
  143. sierra/plugins/proc/collate/__init__.py +15 -0
  144. sierra/plugins/proc/collate/cmdline.py +47 -0
  145. sierra/plugins/proc/collate/plugin.py +271 -0
  146. sierra/plugins/proc/compress/__init__.py +18 -0
  147. sierra/plugins/proc/compress/cmdline.py +47 -0
  148. sierra/plugins/proc/compress/plugin.py +123 -0
  149. sierra/plugins/proc/decompress/__init__.py +18 -0
  150. sierra/plugins/proc/decompress/plugin.py +96 -0
  151. sierra/plugins/proc/imagize/__init__.py +15 -0
  152. sierra/plugins/proc/imagize/cmdline.py +49 -0
  153. sierra/plugins/proc/imagize/plugin.py +270 -0
  154. sierra/plugins/proc/modelrunner/__init__.py +16 -0
  155. sierra/plugins/proc/modelrunner/plugin.py +250 -0
  156. sierra/plugins/proc/statistics/__init__.py +15 -0
  157. sierra/plugins/proc/statistics/cmdline.py +64 -0
  158. sierra/plugins/proc/statistics/plugin.py +390 -0
  159. sierra/plugins/{hpc → prod}/__init__.py +1 -0
  160. sierra/plugins/prod/graphs/__init__.py +18 -0
  161. sierra/plugins/prod/graphs/cmdline.py +269 -0
  162. sierra/plugins/prod/graphs/collate.py +279 -0
  163. sierra/plugins/prod/graphs/inter/__init__.py +13 -0
  164. sierra/plugins/prod/graphs/inter/generate.py +83 -0
  165. sierra/plugins/prod/graphs/inter/heatmap.py +86 -0
  166. sierra/plugins/prod/graphs/inter/line.py +134 -0
  167. sierra/plugins/prod/graphs/intra/__init__.py +15 -0
  168. sierra/plugins/prod/graphs/intra/generate.py +202 -0
  169. sierra/plugins/prod/graphs/intra/heatmap.py +74 -0
  170. sierra/plugins/prod/graphs/intra/line.py +114 -0
  171. sierra/plugins/prod/graphs/plugin.py +103 -0
  172. sierra/plugins/prod/graphs/targets.py +63 -0
  173. sierra/plugins/prod/render/__init__.py +18 -0
  174. sierra/plugins/prod/render/cmdline.py +72 -0
  175. sierra/plugins/prod/render/plugin.py +282 -0
  176. sierra/plugins/storage/__init__.py +5 -0
  177. sierra/plugins/storage/arrow/__init__.py +18 -0
  178. sierra/plugins/storage/arrow/plugin.py +38 -0
  179. sierra/plugins/storage/csv/__init__.py +9 -0
  180. sierra/plugins/storage/csv/plugin.py +12 -5
  181. sierra/version.py +3 -2
  182. sierra_research-1.5.0.dist-info/METADATA +238 -0
  183. sierra_research-1.5.0.dist-info/RECORD +186 -0
  184. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/WHEEL +1 -2
  185. sierra/core/experiment/xml.py +0 -454
  186. sierra/core/generators/controller_generator_parser.py +0 -34
  187. sierra/core/generators/exp_creator.py +0 -351
  188. sierra/core/generators/exp_generators.py +0 -142
  189. sierra/core/graphs/scatterplot2D.py +0 -109
  190. sierra/core/graphs/stacked_line_graph.py +0 -249
  191. sierra/core/graphs/stacked_surface_graph.py +0 -220
  192. sierra/core/graphs/summary_line_graph.py +0 -369
  193. sierra/core/hpc/cmdline.py +0 -142
  194. sierra/core/models/graphs.py +0 -87
  195. sierra/core/pipeline/stage2/exp_runner.py +0 -286
  196. sierra/core/pipeline/stage3/imagizer.py +0 -149
  197. sierra/core/pipeline/stage3/run_collator.py +0 -317
  198. sierra/core/pipeline/stage3/statistics_calculator.py +0 -478
  199. sierra/core/pipeline/stage4/graph_collator.py +0 -319
  200. sierra/core/pipeline/stage4/inter_exp_graph_generator.py +0 -240
  201. sierra/core/pipeline/stage4/intra_exp_graph_generator.py +0 -317
  202. sierra/core/pipeline/stage4/model_runner.py +0 -168
  203. sierra/core/pipeline/stage4/rendering.py +0 -283
  204. sierra/core/pipeline/stage4/yaml_config_loader.py +0 -103
  205. sierra/core/pipeline/stage5/inter_scenario_comparator.py +0 -328
  206. sierra/core/pipeline/stage5/intra_scenario_comparator.py +0 -989
  207. sierra/core/platform.py +0 -493
  208. sierra/core/plugin_manager.py +0 -369
  209. sierra/core/root_dirpath_generator.py +0 -241
  210. sierra/plugins/hpc/adhoc/plugin.py +0 -125
  211. sierra/plugins/hpc/local/plugin.py +0 -81
  212. sierra/plugins/hpc/pbs/__init__.py +0 -9
  213. sierra/plugins/hpc/pbs/plugin.py +0 -126
  214. sierra/plugins/hpc/slurm/__init__.py +0 -9
  215. sierra/plugins/hpc/slurm/plugin.py +0 -130
  216. sierra/plugins/platform/__init__.py +0 -9
  217. sierra/plugins/platform/argos/__init__.py +0 -9
  218. sierra/plugins/platform/argos/generators/platform_generators.py +0 -383
  219. sierra/plugins/platform/argos/plugin.py +0 -337
  220. sierra/plugins/platform/argos/variables/arena_shape.py +0 -145
  221. sierra/plugins/platform/argos/variables/cameras.py +0 -243
  222. sierra/plugins/platform/argos/variables/constant_density.py +0 -136
  223. sierra/plugins/platform/argos/variables/exp_setup.py +0 -113
  224. sierra/plugins/platform/argos/variables/population_constant_density.py +0 -175
  225. sierra/plugins/platform/argos/variables/population_size.py +0 -102
  226. sierra/plugins/platform/argos/variables/population_variable_density.py +0 -132
  227. sierra/plugins/platform/argos/variables/rendering.py +0 -104
  228. sierra/plugins/platform/ros1gazebo/__init__.py +0 -9
  229. sierra/plugins/platform/ros1gazebo/cmdline.py +0 -213
  230. sierra/plugins/platform/ros1gazebo/generators/platform_generators.py +0 -137
  231. sierra/plugins/platform/ros1gazebo/plugin.py +0 -335
  232. sierra/plugins/platform/ros1gazebo/variables/__init__.py +0 -10
  233. sierra/plugins/platform/ros1gazebo/variables/population_size.py +0 -204
  234. sierra/plugins/platform/ros1robot/__init__.py +0 -9
  235. sierra/plugins/platform/ros1robot/cmdline.py +0 -175
  236. sierra/plugins/platform/ros1robot/generators/platform_generators.py +0 -112
  237. sierra/plugins/platform/ros1robot/plugin.py +0 -373
  238. sierra/plugins/platform/ros1robot/variables/__init__.py +0 -10
  239. sierra/plugins/platform/ros1robot/variables/population_size.py +0 -146
  240. sierra/plugins/robot/__init__.py +0 -9
  241. sierra/plugins/robot/turtlebot3/__init__.py +0 -9
  242. sierra/plugins/robot/turtlebot3/plugin.py +0 -194
  243. sierra_research-1.3.6.data/data/share/man/man1/sierra-cli.1 +0 -2349
  244. sierra_research-1.3.6.data/data/share/man/man7/sierra-examples.7 +0 -488
  245. sierra_research-1.3.6.data/data/share/man/man7/sierra-exec-envs.7 +0 -331
  246. sierra_research-1.3.6.data/data/share/man/man7/sierra-glossary.7 +0 -285
  247. sierra_research-1.3.6.data/data/share/man/man7/sierra-platforms.7 +0 -358
  248. sierra_research-1.3.6.data/data/share/man/man7/sierra-usage.7 +0 -725
  249. sierra_research-1.3.6.data/data/share/man/man7/sierra.7 +0 -78
  250. sierra_research-1.3.6.dist-info/METADATA +0 -500
  251. sierra_research-1.3.6.dist-info/RECORD +0 -133
  252. sierra_research-1.3.6.dist-info/top_level.txt +0 -1
  253. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/entry_points.txt +0 -0
  254. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -10,169 +10,40 @@ import logging
10
10
  import argparse
11
11
  import copy
12
12
  import pathlib
13
+ import itertools
13
14
 
14
15
  # 3rd party packages
15
16
  import implements
16
17
 
18
+ # Project packages
17
19
  from sierra.core.variables import base_variable
18
20
  from sierra.core import utils
19
- from sierra.core.experiment import definition, xml
20
-
21
-
22
- import sierra.core.plugin_manager as pm
21
+ from sierra.core.experiment import definition
22
+ import sierra.core.plugin as pm
23
23
  from sierra.core import types, config
24
+ from sierra.core.graphs import bcbridge
24
25
 
25
26
 
26
27
  class IQueryableBatchCriteria(implements.Interface):
27
28
  """Mixin interface for criteria which can be queried during stage {1,2}.
28
29
 
29
30
  Used to extract additional information needed for configuring some
30
- :term:`Platforms <Platform>` and execution environments.
31
-
32
- """
33
-
34
- def n_robots(self, exp_num: int) -> int:
35
- """
36
- Return the # of robots used for a given :term:`Experiment`.
37
- """
38
- raise NotImplementedError
39
-
40
-
41
- class IConcreteBatchCriteria(implements.Interface):
42
- """
43
- 'Final' interface for user-visible batch criteria.
44
- """
45
-
46
- def graph_xticks(self,
47
- cmdopts: types.Cmdopts,
48
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[float]:
49
- """Calculate X axis ticks for graph generation.
50
-
51
- Arguments:
52
-
53
- cmdopts: Dictionary of parsed command line options.
54
-
55
- exp_names: If not None, then this list of directories will be used
56
- to calculate the ticks, rather than the results of
57
- gen_exp_names().
58
-
59
- """
60
-
61
- raise NotImplementedError
62
-
63
- def graph_xticklabels(self,
64
- cmdopts: types.Cmdopts,
65
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[str]:
66
- """Calculate X axis tick labels for graph generation.
67
-
68
- Arguments:
69
-
70
- cmdopts: Dictionary of parsed command line options.
71
-
72
- exp_names: If not None, then these directories will be used to
73
- calculate the labels, rather than the results of
74
- gen_exp_names().
75
-
76
- """
77
- raise NotImplementedError
78
-
79
- def graph_xlabel(self, cmdopts: types.Cmdopts) -> str:
80
- """Get the X-label for a graph.
81
-
82
- Returns:
83
-
84
- The X-label that should be used for the graphs of various
85
- performance measures across batch criteria.
86
-
87
- """
88
- raise NotImplementedError
89
-
90
-
91
- class IBivarBatchCriteria(implements.Interface):
92
- """
93
- Interface for bivariate batch criteria(those with two univariate axes).
94
- """
95
-
96
- def graph_yticks(self,
97
- cmdopts: types.Cmdopts,
98
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[float]:
99
- """
100
- Calculate Y axis ticks for graph generation.
101
-
102
- Arguments:
103
-
104
- cmdopts: Dictionary of parsed command line options.
105
-
106
- exp_names: If not None, then these directories will be used to
107
- calculate the ticks, rather than the results of
108
- gen_exp_names().
109
-
110
- """
111
- raise NotImplementedError
112
-
113
- def graph_yticklabels(self,
114
- cmdopts: types.Cmdopts,
115
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[str]:
116
- """
117
- Calculate X axis ticks for graph generation.
118
-
119
- Arguments:
120
-
121
- cmdopts: Dictionary of parsed command line options.
122
-
123
- exp_names: If not None, then these directories will be used to
124
- calculate the labels, rather than the results of
125
- gen_exp_names().
126
- """
127
- raise NotImplementedError
128
-
129
- def graph_ylabel(self, cmdopts: types.Cmdopts) -> str:
130
- """
131
- Get the Y-label for a graph.
132
-
133
- Returns:p
134
-
135
- The Y-label that should be used for the graphs of various
136
- performance measures across batch criteria. Only needed by bivar
137
- batch criteria.
138
- """
139
- raise NotImplementedError
140
-
141
-
142
- class IBatchCriteriaType(implements.Interface):
143
- """Mixin interface for criteria for querying univariate/bivariate.
31
+ :term:`Engines <Engine>` and execution environments.
144
32
 
145
33
  """
146
34
 
147
- def is_bivar(self) -> bool:
148
- """
149
- Determine if the batch criteria is bivariate.
150
-
151
- Returns:
152
-
153
- `True` if this class is a bivariate batch criteria instance, and
154
- `False` otherwise.
155
- """
156
- raise NotImplementedError
157
-
158
- def is_univar(self) -> bool:
35
+ def n_agents(self, exp_num: int) -> int:
159
36
  """
160
- Determine if the batch criteria is univariate.
161
-
162
- Returns:
163
-
164
- `True` if this class is a univar batch criteria instance, and
165
- `False` otherwise.
37
+ Return the # of agents used for a given :term:`Experiment`.
166
38
  """
167
39
  raise NotImplementedError
168
40
 
169
41
 
170
42
  @implements.implements(base_variable.IBaseVariable)
171
- class BatchCriteria():
172
- """Defines experiments via lists of sets of changes to make to an XML file.
43
+ class BaseBatchCriteria:
44
+ """Defines experiments via lists of sets of changes to make to an expdef.
173
45
 
174
46
  Attributes:
175
-
176
47
  cli_arg: Unparsed batch criteria string from command line.
177
48
 
178
49
  main_config: Parsed dictionary of main YAML configuration.
@@ -182,72 +53,72 @@ class BatchCriteria():
182
53
 
183
54
  """
184
55
 
185
- def __init__(self,
186
- cli_arg: str,
187
- main_config: types.YAMLDict,
188
- batch_input_root: pathlib.Path) -> None:
189
- self.cli_arg = cli_arg
56
+ def __init__(
57
+ self, cli_arg: str, main_config: types.YAMLDict, batch_input_root: pathlib.Path
58
+ ) -> None:
59
+
60
+ # 2025-09-21 [JRH]: The "name" of the batch criteria is just whatever is
61
+ # passed on the cmdline.
62
+ self.name = cli_arg
190
63
  self.main_config = main_config
191
64
  self.batch_input_root = batch_input_root
192
65
 
193
- self.cat_str = cli_arg.split('.')[0]
194
- self.def_str = '.'.join(cli_arg.split('.')[1:])
66
+ self.cat_str = cli_arg.split(".")[0]
67
+ self.def_str = ".".join(cli_arg.split(".")[1:])
195
68
  self.logger = logging.getLogger(__name__)
196
69
 
197
70
  # Stub out IBaseVariable because all concrete batch criteria only implement
198
71
  # a subset of them.
199
- def gen_attr_changelist(self) -> tp.List[xml.AttrChangeSet]:
72
+ def gen_attr_changelist(self) -> tp.List[definition.AttrChangeSet]:
200
73
  return []
201
74
 
202
- def gen_tag_rmlist(self) -> tp.List[xml.TagRmList]:
75
+ def gen_tag_rmlist(self) -> tp.List[definition.ElementRmList]:
203
76
  return []
204
77
 
205
- def gen_tag_addlist(self) -> tp.List[xml.TagAddList]:
78
+ def gen_element_addlist(self) -> tp.List[definition.ElementAddList]:
206
79
  return []
207
80
 
208
81
  def gen_files(self) -> None:
209
82
  pass
210
83
 
211
- def gen_exp_names(self, cmdopts: types.Cmdopts) -> tp.List[str]:
212
- """
213
- Generate list of experiment names from the criteria.
214
-
215
- Used for creating unique directory names for each experiment in the
216
- batch.
84
+ def cardinality(self) -> int:
85
+ return -1
217
86
 
218
- Returns:
219
-
220
- List of experiments names for current experiment.
87
+ def gen_exp_names(self) -> tp.List[str]:
88
+ raise NotImplementedError
221
89
 
222
- """
223
- return []
90
+ def computable_exp_scenario_name(self) -> bool:
91
+ return False
224
92
 
225
93
  def arena_dims(self, cmdopts: types.Cmdopts) -> tp.List[utils.ArenaExtent]:
226
94
  """Get the arena dimensions used for each experiment in the batch.
227
95
 
228
96
  Not applicable to all criteria.
229
97
 
230
- Must be implemented on a per-platform basis, as different platforms have
98
+ Must be implemented on a per-engine basis, as different engines have
231
99
  different means of computing the size of the arena.
232
100
 
233
101
  """
234
- module = pm.pipeline.get_plugin_module(cmdopts['platform'])
235
- assert hasattr(module, 'arena_dims_from_criteria'), \
236
- f"Platform plugin {module.__name__} does not implement arena_dims_from_criteria()"
102
+ module = pm.pipeline.get_plugin_module(cmdopts["engine"])
103
+ assert hasattr(
104
+ module, "arena_dims_from_criteria"
105
+ ), f"Engine plugin {module.__name__} does not implement arena_dims_from_criteria()"
237
106
 
238
107
  return module.arena_dims_from_criteria(self)
239
108
 
240
109
  def n_exp(self) -> int:
241
110
  from sierra.core.experiment import spec
111
+
242
112
  scaffold_spec = spec.scaffold_spec_factory(self)
243
113
  return scaffold_spec.n_exps
244
114
 
245
115
  def pickle_exp_defs(self, cmdopts: types.Cmdopts) -> None:
246
116
  from sierra.core.experiment import spec
117
+
247
118
  scaffold_spec = spec.scaffold_spec_factory(self)
248
119
 
249
120
  for exp in range(0, scaffold_spec.n_exps):
250
- exp_dirname = self.gen_exp_names(cmdopts)[exp]
121
+ exp_dirname = self.gen_exp_names()[exp]
251
122
  # Pickling of batch criteria experiment definitions is the FIRST set
252
123
  # of changes to be pickled--all other changes come after. We append
253
124
  # to the pickle file by default, which allows any number of
@@ -264,128 +135,127 @@ class BatchCriteria():
264
135
  exp_defi[0].pickle(pkl_path, delete=True)
265
136
  exp_defi[1].pickle(pkl_path, delete=False)
266
137
 
267
- def scaffold_exps(self,
268
- batch_def: definition.XMLExpDef,
269
- cmdopts: types.Cmdopts) -> None:
138
+ def scaffold_exps(
139
+ self, batch_def: definition.BaseExpDef, cmdopts: types.Cmdopts
140
+ ) -> None:
270
141
  """Scaffold a batch experiment.
271
142
 
272
- Takes the raw template input file and apply XML modifications from the
143
+ Takes the raw template input file and apply expdef modifications from the
273
144
  batch criteria for all experiments, and save the result in each
274
145
  experiment's input directory.
275
146
 
276
147
  """
277
148
 
278
149
  from sierra.core.experiment import spec
150
+
279
151
  scaffold_spec = spec.scaffold_spec_factory(self, log=True)
280
152
 
281
153
  for i in range(0, scaffold_spec.n_exps):
282
154
  modsi = scaffold_spec.mods[i]
283
155
  expi_def = copy.deepcopy(batch_def)
284
- self._scaffold_expi(expi_def,
285
- modsi,
286
- scaffold_spec.is_compound,
287
- i,
288
- cmdopts)
156
+ self._scaffold_expi(expi_def, modsi, scaffold_spec.is_compound, i, cmdopts)
289
157
 
290
158
  n_exp_dirs = len(list(self.batch_input_root.iterdir()))
291
159
  if scaffold_spec.n_exps != n_exp_dirs:
292
- msg1 = (f"Size of batch experiment ({scaffold_spec.n_exps}) != "
293
- f"# exp dirs ({n_exp_dirs}): possibly caused by:")
294
- msg2 = (f"(1) Changing bc w/o changing the generation root "
295
- f"({self.batch_input_root})")
296
- msg3 = (f"(2) Sharing {self.batch_input_root} between different "
297
- f"batch criteria")
160
+ msg1 = (
161
+ f"Size of batch experiment ({scaffold_spec.n_exps}) != "
162
+ f"# exp dirs ({n_exp_dirs}): possibly caused by:"
163
+ )
164
+ msg2 = (
165
+ f"(1) Changing bc w/o changing the generation root "
166
+ f"({self.batch_input_root})"
167
+ )
168
+ msg3 = (
169
+ f"(2) Sharing {self.batch_input_root} between different "
170
+ f"batch criteria"
171
+ )
298
172
 
299
173
  self.logger.fatal(msg1)
300
174
  self.logger.fatal(msg2)
301
175
  self.logger.fatal(msg3)
302
176
  raise RuntimeError("Batch experiment size/# exp dir mismatch")
303
177
 
304
- def _scaffold_expi(self,
305
- expi_def: definition.XMLExpDef,
306
- modsi,
307
- is_compound: bool,
308
- i: int,
309
- cmdopts: types.Cmdopts) -> None:
310
- exp_dirname = self.gen_exp_names(cmdopts)[i]
178
+ def _scaffold_expi(
179
+ self,
180
+ expi_def: definition.BaseExpDef,
181
+ modsi,
182
+ is_compound: bool,
183
+ i: int,
184
+ cmdopts: types.Cmdopts,
185
+ ) -> None:
186
+
187
+ exp_dirname = self.gen_exp_names()[i]
311
188
  exp_input_root = self.batch_input_root / exp_dirname
312
189
 
313
- utils.dir_create_checked(exp_input_root,
314
- exist_ok=cmdopts['exp_overwrite'])
190
+ utils.dir_create_checked(exp_input_root, exist_ok=cmdopts["exp_overwrite"])
315
191
 
316
192
  if not is_compound:
317
- self.logger.debug(("Applying %s XML modifications from '%s' for "
318
- "exp%s in %s"),
319
- len(modsi),
320
- self.cli_arg,
321
- i,
322
- exp_dirname)
193
+ self.logger.debug(
194
+ ("Applying %s expdef mods from '%s' for exp%s in %s"),
195
+ len(modsi),
196
+ self.name,
197
+ i,
198
+ exp_dirname,
199
+ )
323
200
 
324
201
  for mod in modsi:
325
- if isinstance(mod, xml.AttrChange):
202
+ if isinstance(mod, definition.AttrChange):
326
203
  expi_def.attr_change(mod.path, mod.attr, mod.value)
327
- elif isinstance(mod, xml.TagAdd):
328
- assert mod.path is not None, \
329
- "Cannot add root {mode.tag} during scaffolding"
330
- expi_def.tag_add(mod.path,
331
- mod.tag,
332
- mod.attr,
333
- mod.allow_dup)
204
+ elif isinstance(mod, definition.ElementAdd):
205
+ assert (
206
+ mod.path is not None
207
+ ), "Cannot add root {mode.tag} during scaffolding"
208
+ expi_def.element_add(mod.path, mod.tag, mod.attr, mod.allow_dup)
334
209
  else:
335
- self.logger.debug(("Applying %s XML modifications from '%s' for "
336
- "exp%s in %s"),
337
- len(modsi[0]) + len(modsi[1]),
338
- self.cli_arg,
339
- i,
340
- exp_dirname)
210
+ self.logger.debug(
211
+ ("Applying %s expdef modifications from '%s' for exp%s in %s"),
212
+ len(modsi[0]) + len(modsi[1]),
213
+ self.name,
214
+ i,
215
+ exp_dirname,
216
+ )
341
217
 
342
218
  # Mods are a tuple for compound specs: adds, changes. We do adds
343
219
  # first, in case some insane person wants to use the second batch
344
220
  # criteria to modify something they just added.
345
221
  for add in modsi[0]:
346
- expi_def.tag_add(add.path,
347
- add.tag,
348
- add.attr,
349
- add.allow_dup)
222
+ expi_def.element_add(add.path, add.tag, add.attr, add.allow_dup)
350
223
  for chg in modsi[1]:
351
- expi_def.attr_change(chg.path,
352
- chg.attr,
353
- chg.value)
224
+ expi_def.attr_change(chg.path, chg.attr, chg.value)
354
225
 
355
226
  # This will be the "template" input file used to generate the input
356
227
  # files for each experimental run in the experiment
357
- wr_config = xml.WriterConfig([{'src_parent': None,
358
- 'src_tag': '.',
359
- 'opath_leaf': None,
360
- 'create_tags': None,
361
- 'dest_parent': None
362
- }])
228
+ fmt = pm.pipeline.get_plugin_module(cmdopts["expdef"])
229
+ wr_config = definition.WriterConfig(
230
+ [
231
+ {
232
+ "src_parent": None,
233
+ "src_tag": fmt.root_querypath(),
234
+ "opath_leaf": None,
235
+ "new_children": None,
236
+ "new_children_parent": None,
237
+ }
238
+ ]
239
+ )
363
240
  expi_def.write_config_set(wr_config)
364
- opath = utils.exp_template_path(cmdopts,
365
- self.batch_input_root,
366
- exp_dirname)
241
+ opath = utils.exp_template_path(cmdopts, self.batch_input_root, exp_dirname)
367
242
  expi_def.write(opath)
368
243
 
369
244
 
370
- @implements.implements(IBatchCriteriaType)
371
- class UnivarBatchCriteria(BatchCriteria):
245
+ class UnivarBatchCriteria(BaseBatchCriteria):
372
246
  """
373
247
  Base class for a univariate batch criteria.
374
248
  """
375
249
 
376
- #
377
- # IBatchCriteriaType overrides
378
- #
379
-
380
- def is_bivar(self) -> bool:
381
- return False
250
+ def cardinality(self) -> int:
251
+ return 1
382
252
 
383
- def is_univar(self) -> bool:
384
- return True
253
+ def gen_exp_names(self) -> tp.List[str]:
254
+ return [f"c1-exp{i}" for i in range(0, self.n_exp())]
385
255
 
386
- def populations(self,
387
- cmdopts: types.Cmdopts,
388
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[int]:
256
+ def populations(
257
+ self, cmdopts: types.Cmdopts, exp_names: tp.Optional[tp.List[str]] = None
258
+ ) -> tp.List[int]:
389
259
  """
390
260
  Calculate system sizes used the batch experiment, sorted.
391
261
 
@@ -402,142 +272,198 @@ class UnivarBatchCriteria(BatchCriteria):
402
272
  if exp_names is not None:
403
273
  names = exp_names
404
274
  else:
405
- names = self.gen_exp_names(cmdopts)
275
+ names = self.gen_exp_names()
406
276
 
407
- module = pm.pipeline.get_plugin_module(cmdopts['platform'])
277
+ module1 = pm.pipeline.get_plugin_module(cmdopts["engine"])
278
+ module2 = pm.pipeline.get_plugin_module(cmdopts["expdef"])
408
279
  for d in names:
409
280
  path = self.batch_input_root / d / config.kPickleLeaf
410
- exp_def = definition.unpickle(path)
281
+ exp_def = module2.unpickle(path)
282
+
283
+ sizes.append(
284
+ module1.population_size_from_pickle(exp_def, self.main_config, cmdopts)
285
+ )
411
286
 
412
- sizes.append(module.population_size_from_pickle(exp_def,
413
- self.main_config,
414
- cmdopts))
415
287
  return sizes
416
288
 
417
289
 
418
- @implements.implements(IBivarBatchCriteria)
419
- @implements.implements(IBatchCriteriaType)
290
+ @implements.implements(bcbridge.IGraphable)
420
291
  @implements.implements(IQueryableBatchCriteria)
421
- class BivarBatchCriteria(BatchCriteria):
292
+ class XVarBatchCriteria(BaseBatchCriteria):
422
293
  """
423
- Combination of the definition of two separate batch criteria.
294
+ N-dimensional multiple :class:`sierra.core.variables.batch_criteria.UnivarBatchCriteria`.
424
295
 
425
296
  .. versionchanged:: 1.2.20
426
297
 
427
- Bivariate batch criteria can be compound: one criteria can create and
428
- the other modify XML tags to create an experiment definition.
429
-
298
+ Batch criteria can be compound: one criteria can create and the other
299
+ modify expdef elements to create an experiment definition.
430
300
  """
431
301
 
432
- def __init__(self,
433
- criteria1: IConcreteBatchCriteria,
434
- criteria2: IConcreteBatchCriteria) -> None:
435
- BatchCriteria.__init__(self,
436
- '+'.join([criteria1.cli_arg, criteria2.cli_arg]),
437
- criteria1.main_config,
438
- criteria1.batch_input_root)
439
- self.criteria1 = criteria1
440
- self.criteria2 = criteria2
441
- #
442
- # IBatchCriteriaType overrides
443
- #
444
-
445
- def is_bivar(self) -> bool:
446
- return True
447
-
448
- def is_univar(self) -> bool:
449
- return False
302
+ def __init__(self, criterias: tp.List[BaseBatchCriteria]) -> None:
303
+ BaseBatchCriteria.__init__(
304
+ self,
305
+ "+".join([c.name for c in criterias]),
306
+ criterias[0].main_config,
307
+ criterias[0].batch_input_root,
308
+ )
309
+ self.criterias = criterias
450
310
 
451
- def gen_attr_changelist(self) -> tp.List[xml.AttrChangeSet]:
452
- list1 = self.criteria1.gen_attr_changelist()
453
- list2 = self.criteria2.gen_attr_changelist()
454
- ret = []
311
+ def cardinality(self) -> int:
312
+ return len(self.criterias)
455
313
 
456
- if list1 and list2:
457
- for l1 in list1:
458
- for l2 in list2:
459
- ret.append(l1 | l2)
314
+ def computable_exp_scenario_name(self) -> bool:
315
+ return any(c.computable_exp_scenario_name() for c in self.criterias)
460
316
 
461
- elif list1:
462
- ret = list1
317
+ def gen_attr_changelist(self) -> tp.List[definition.AttrChangeSet]:
318
+ changes = [c.gen_attr_changelist() for c in self.criterias]
463
319
 
464
- elif list2:
465
- ret = list2
320
+ # Flatten each list of sets into a single list of items
321
+ flattened_lists = []
466
322
 
467
- return ret
323
+ for list_of_sets in changes:
324
+ flattened_list = [] # type: tp.List[definition.AttrChangeSet]
325
+ for s in list_of_sets:
326
+ flattened_list.append(s)
468
327
 
469
- def gen_tag_addlist(self) -> tp.List[xml.TagAddList]:
470
- list1 = self.criteria1.gen_tag_addlist()
471
- list2 = self.criteria2.gen_tag_addlist()
472
- ret = []
328
+ flattened_lists.append(flattened_list)
473
329
 
474
- if list1 and list2:
475
- for l1 in list1:
476
- for l2 in list2:
477
- l1.extend(l2)
478
- ret.append(l1)
479
- elif list1:
480
- ret = list1
330
+ # Use itertools.product to get all combinations
331
+ result = []
332
+ for combination in itertools.product(*flattened_lists):
333
+ combined = definition.AttrChangeSet()
334
+ # Add all changes from each AttrChangeSet in the combination
335
+ for change_set in combination:
336
+ for change in change_set:
337
+ combined.add(change)
481
338
 
482
- elif list2:
483
- ret = list2
339
+ result.append(combined)
484
340
 
485
- return ret
341
+ return result
486
342
 
487
- def gen_tag_rmlist(self) -> tp.List[xml.TagRmList]:
488
- ret = self.criteria1.gen_tag_rmlist()
489
- ret.extend(self.criteria2.gen_tag_rmlist())
490
- return ret
343
+ def gen_element_addlist(self) -> tp.List[definition.ElementAddList]:
344
+ adds = [c.gen_element_addlist() for c in self.criterias]
345
+
346
+ # Create combinations and combine ElementAddList objects
347
+ result = []
348
+ for combo in itertools.product(*adds):
349
+ combined = definition.ElementAddList()
350
+
351
+ # Add all ElementAdd objects from each ElementAddList in the combo
352
+ for elem_add_list in combo:
353
+ for elem_add in elem_add_list:
354
+ combined.append(elem_add)
355
+
356
+ result.append(combined)
357
+
358
+ return result
359
+
360
+ def gen_tag_rmlist(self) -> tp.List[definition.ElementRmList]:
361
+ rms = [c.gen_tag_rmlist() for c in self.criterias]
491
362
 
492
- def gen_exp_names(self, cmdopts: types.Cmdopts) -> tp.List[str]:
363
+ # Create combinations and combine ElementRmList objects
364
+ result = []
365
+ for combo in itertools.product(*rms):
366
+ combined = definition.ElementRmList()
367
+
368
+ # Add all ElementRm objects from each ElementRmList in the combo
369
+ for elem_rm_list in combo:
370
+ for elem_rm in elem_rm_list:
371
+ combined.append(elem_rm)
372
+ result.append(combined)
373
+
374
+ return result
375
+
376
+ def gen_exp_names(self) -> tp.List[str]:
493
377
  """
494
378
  Generate a SORTED list of strings for all experiment names.
495
379
 
496
- These will be used as directory LEAF names--and don't include the
497
- parents.
380
+ These will be used as directory LEAF names, and don't include the
381
+ parents. Basically, this is a flattened list of permutations of all
382
+ ``gen_exp_names()`` for each batch criteria.
498
383
 
499
384
  """
500
- list1 = self.criteria1.gen_exp_names(cmdopts)
501
- list2 = self.criteria2.gen_exp_names(cmdopts)
502
- ret = []
503
385
 
504
- for l1 in list1:
505
- for l2 in list2:
506
- ret.append('+'.join(['c1-' + l1, 'c2-' + l2]))
386
+ # Collect all criteria lists with their prefixes
387
+ criteria_lists = []
388
+ for i, criteria in enumerate(self.criterias, 1):
389
+ prefixed_names = [f"c{i}-exp{j}" for j in range(0, criteria.n_exp())]
390
+ criteria_lists.append(prefixed_names)
391
+
392
+ # Generate all combinations using itertools.product
393
+ ret = []
394
+ for combination in itertools.product(*criteria_lists):
395
+ ret.append("+".join(combination))
507
396
 
508
397
  return ret
509
398
 
510
- def populations(self, cmdopts: types.Cmdopts) -> tp.List[tp.List[int]]:
511
- """Generate a 2D array of system sizes used the batch experiment.
399
+ def populations(self, cmdopts: types.Cmdopts) -> list:
400
+ """Generate a N-D array of system sizes used the batch experiment.
512
401
 
513
402
  Sizes are in the same order as the directories returned from
514
- `gen_exp_names()` for each criteria along each axis.
403
+ ``gen_exp_names()`` for each criteria along each axis.
515
404
 
516
405
  """
517
- names = self.gen_exp_names(cmdopts)
406
+ names = self.gen_exp_names()
407
+ criteria_dims = []
408
+ criteria_counts = []
409
+
410
+ for criteria in self.criterias:
411
+ exp_names = criteria.gen_exp_names()
412
+ n_chgs = len(criteria.gen_attr_changelist())
413
+ n_adds = len(criteria.gen_element_addlist())
414
+
415
+ criteria_dims.append(len(exp_names))
416
+ criteria_counts.append(n_chgs + n_adds)
518
417
 
519
- sizes = [[0 for col in self.criteria2.gen_exp_names(
520
- cmdopts)] for row in self.criteria1.gen_exp_names(cmdopts)]
418
+ # Create multi-dimensional nested list initialized with zeros
419
+ def create_nested_list(dimensions: tp.List[int]) -> list:
420
+ if len(dimensions) == 1:
421
+ return [0] * dimensions[0]
422
+ return [create_nested_list(dimensions[1:]) for _ in range(dimensions[0])]
521
423
 
522
- n_chgs2 = len(self.criteria2.gen_attr_changelist())
523
- n_adds2 = len(self.criteria2.gen_tag_addlist())
424
+ sizes = create_nested_list(criteria_dims)
425
+
426
+ # Get plugin modules
427
+ module1 = pm.pipeline.get_plugin_module(cmdopts["engine"])
428
+ module2 = pm.pipeline.get_plugin_module(cmdopts["expdef"])
429
+
430
+ # Calculate total combinations for index conversion
431
+ total_combinations = 1
432
+ for count in criteria_counts:
433
+ total_combinations *= count
524
434
 
525
- module = pm.pipeline.get_plugin_module(cmdopts['platform'])
526
435
  for d in names:
527
436
  pkl_path = self.batch_input_root / d / config.kPickleLeaf
528
- exp_def = definition.unpickle(pkl_path)
437
+ exp_def = module2.unpickle(pkl_path)
529
438
 
439
+ # Convert linear index to multi-dimensional indices
530
440
  index = names.index(d)
531
- i = int(index / (n_chgs2 + n_adds2))
532
- j = index % (n_chgs2 + n_adds2)
533
- sizes[i][j] = module.population_size_from_pickle(exp_def,
534
- self.main_config,
535
- cmdopts)
441
+ indices = []
442
+ remaining_index = index
443
+
444
+ for i in range(len(criteria_counts)):
445
+ # Calculate stride for this dimension
446
+ stride = 1
447
+ for j in range(i + 1, len(criteria_counts)):
448
+ stride *= criteria_counts[j]
449
+
450
+ # Calculate index for this dimension
451
+ dim_index = remaining_index // stride
452
+ indices.append(dim_index)
453
+ remaining_index = remaining_index % stride
454
+
455
+ # Set the population size at the calculated indices
456
+ current_level = sizes
457
+ for i, idx in enumerate(indices[:-1]):
458
+ current_level = current_level[idx]
459
+ current_level[indices[-1]] = module1.population_size_from_pickle(
460
+ exp_def, self.main_config, cmdopts
461
+ )
536
462
 
537
463
  return sizes
538
464
 
539
465
  def exp_scenario_name(self, exp_num: int) -> str:
540
- """Given the expeperiment number, compute a parsable scenario name.
466
+ """Given the experiment number, compute a parsable scenario name.
541
467
 
542
468
  It is necessary to query this function after generating the changelist
543
469
  in order to create generator classes for each experiment in the batch
@@ -546,181 +472,161 @@ class BivarBatchCriteria(BatchCriteria):
546
472
  Can only be called if constant density is one of the sub-criteria.
547
473
 
548
474
  """
549
- if hasattr(self.criteria1, 'exp_scenario_name'):
550
- return self.criteria1.exp_scenario_name(int(exp_num /
551
- len(self.criteria2.gen_attr_changelist())))
552
- if hasattr(self.criteria2, 'exp_scenario_name'):
553
- return self.criteria2.exp_scenario_name(int(exp_num % len(self.criteria2.gen_attr_changelist())))
554
- else:
555
- raise RuntimeError(
556
- "Bivariate batch criteria does not contain constant density")
557
-
558
- def graph_xticks(self,
559
- cmdopts: types.Cmdopts,
560
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[float]:
561
- names = []
562
- all_dirs = utils.exp_range_calc(cmdopts,
563
- cmdopts['batch_output_root'],
564
- self)
565
-
566
- for c1 in self.criteria1.gen_exp_names(cmdopts):
567
- for x in all_dirs:
568
- leaf = x.name
569
- if c1 in leaf.split('+')[0]:
570
- names.append(leaf)
571
- break
572
-
573
- return self.criteria1.graph_xticks(cmdopts, names)
574
-
575
- def graph_yticks(self,
576
- cmdopts: types.Cmdopts,
577
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[float]:
578
- names = []
579
- all_dirs = utils.exp_range_calc(cmdopts,
580
- cmdopts['batch_output_root'],
581
- self)
582
-
583
- for c2 in self.criteria2.gen_exp_names(cmdopts):
584
- for y in all_dirs:
585
- leaf = y.name
586
- if c2 in leaf.split('+')[1]:
587
- names.append(leaf)
588
- break
589
-
590
- return self.criteria2.graph_xticks(cmdopts, names)
591
-
592
- def graph_xticklabels(self,
593
- cmdopts: types.Cmdopts,
594
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[str]:
595
- names = []
596
- all_dirs = utils.exp_range_calc(cmdopts,
597
- cmdopts['batch_output_root'],
598
- self)
599
-
600
- for c1 in self.criteria1.gen_exp_names(cmdopts):
601
- for x in all_dirs:
602
- leaf = x.name
603
- if c1 in leaf.split('+')[0]:
604
- names.append(leaf)
605
- break
606
-
607
- return self.criteria1.graph_xticklabels(cmdopts, names)
608
-
609
- def graph_yticklabels(self,
610
- cmdopts: types.Cmdopts,
611
- exp_names: tp.Optional[tp.List[str]] = None) -> tp.List[str]:
612
- names = []
613
- all_dirs = utils.exp_range_calc(cmdopts,
614
- cmdopts['batch_output_root'],
615
- self)
616
-
617
- for c2 in self.criteria2.gen_exp_names(cmdopts):
618
- for y in all_dirs:
619
- leaf = y.name
620
- if c2 in leaf.split('+')[1]:
621
- names.append(leaf)
622
- break
623
-
624
- return self.criteria2.graph_xticklabels(cmdopts, names)
625
-
626
- def graph_xlabel(self, cmdopts: types.Cmdopts) -> str:
627
- return self.criteria1.graph_xlabel(cmdopts)
628
-
629
- def graph_ylabel(self, cmdopts: types.Cmdopts) -> str:
630
- return self.criteria2.graph_xlabel(cmdopts)
475
+ for criteria in self.criterias:
476
+ if hasattr(criteria, "exp_scenario_name"):
477
+ return criteria.exp_scenario_name(
478
+ int(exp_num / len(criteria.gen_attr_changelist()))
479
+ )
480
+ raise RuntimeError(
481
+ "Batch criteria does not define 'exp_scenario_name()' required for constant density scenarios"
482
+ )
483
+
484
+ def graph_info(
485
+ self,
486
+ cmdopts: types.Cmdopts,
487
+ batch_output_root: tp.Optional[pathlib.Path] = None,
488
+ exp_names: tp.Optional[tp.List[str]] = None,
489
+ ) -> bcbridge.GraphInfo:
490
+ info = bcbridge.GraphInfo(
491
+ cmdopts,
492
+ batch_output_root,
493
+ self.gen_exp_names(),
494
+ )
495
+
496
+ # 2025-07-08 [JRH]: Eventually, this will be replaced with axes
497
+ # selection, but for now, limiting to bivariate is the simpler way to
498
+ # go.
499
+ assert (
500
+ len(self.criterias) <= 2
501
+ ), "Only {univar,bivar} batch criteria graph generation currently supported"
502
+
503
+ exp_names = self.gen_exp_names()
504
+ if self.cardinality() == 1:
505
+ info1 = self.criterias[0].graph_info(
506
+ cmdopts, exp_names=exp_names, batch_output_root=batch_output_root
507
+ )
508
+
509
+ info.xticks = info1.xticks
510
+ info.xlabel = info1.xlabel
511
+ info.xticklabels = [str(x) for x in info.xticks]
512
+
513
+ elif self.cardinality() == 2:
514
+ c1_xnames = [f"c1-exp{i}" for i in range(0, self.criterias[0].n_exp())]
515
+ xnames = [d for d in self.gen_exp_names() if any(x in d for x in c1_xnames)]
516
+ c2_ynames = [f"c1-exp{i}" for i in range(0, self.criterias[1].n_exp())]
517
+ ynames = [d for d in self.gen_exp_names() if any(y in d for y in c2_ynames)]
518
+
519
+ info1 = self.criterias[0].graph_info(
520
+ cmdopts, exp_names=xnames, batch_output_root=batch_output_root
521
+ )
522
+ info2 = self.criterias[1].graph_info(
523
+ cmdopts, exp_names=ynames, batch_output_root=batch_output_root
524
+ )
525
+ info.xticks = info1.xticks
526
+ info.xticklabels = [str(x) for x in info.xticks]
527
+ info.yticks = info2.xticks
528
+ info.xlabel = info1.xlabel
529
+ info.ylabel = info2.xlabel
530
+ info.yticklabels = [str(x) for x in info.yticks]
531
+
532
+ return info
631
533
 
632
534
  def set_batch_input_root(self, root: pathlib.Path) -> None:
633
535
  self.batch_input_root = root
634
- self.criteria1.batch_input_root = root
635
- self.criteria2.batch_input_root = root
636
-
637
- def n_robots(self, exp_num: int) -> int:
638
- n_chgs2 = len(self.criteria2.gen_attr_changelist())
639
- n_adds2 = len(self.criteria2.gen_tag_addlist())
640
- i = int(exp_num / (n_chgs2 + n_adds2))
641
- j = exp_num % (n_chgs2 + n_adds2)
642
-
643
- if hasattr(self.criteria1, 'n_robots'):
644
- return self.criteria1.n_robots(i)
645
- elif hasattr(self.criteria2, 'n_robots'):
646
- return self.criteria2.n_robots(j)
647
-
648
- raise NotImplementedError
649
-
650
-
651
- def factory(main_config: types.YAMLDict,
652
- cmdopts: types.Cmdopts,
653
- args: argparse.Namespace,
654
- scenario: tp.Optional[str] = None) -> IConcreteBatchCriteria:
655
- if scenario is None:
656
- scenario = args.scenario
657
-
658
- if len(args.batch_criteria) == 1:
659
- return __univar_factory(main_config,
660
- cmdopts,
661
- args.batch_criteria[0],
662
- scenario)
663
- elif len(args.batch_criteria) == 2:
664
- assert args.batch_criteria[0] != args.batch_criteria[1],\
665
- "Duplicate batch criteria passed"
666
- return __bivar_factory(main_config,
667
- cmdopts,
668
- args.batch_criteria,
669
- scenario)
670
- else:
671
- raise RuntimeError(
672
- "1 or 2 batch criterias must be specified on the cmdline")
673
-
674
-
675
- def __univar_factory(main_config: types.YAMLDict,
676
- cmdopts: types.Cmdopts,
677
- cli_arg: str,
678
- scenario) -> IConcreteBatchCriteria:
536
+ for criteria in self.criterias:
537
+ criteria.batch_input_root = root
538
+
539
+ def n_agents(self, exp_num: int) -> int:
540
+ # Calculate dimensions and counts for each criteria
541
+ criteria_counts = []
542
+ for criteria in self.criterias:
543
+ n_chgs = len(criteria.gen_attr_changelist())
544
+ n_adds = len(criteria.gen_element_addlist())
545
+ criteria_counts.append(n_chgs + n_adds)
546
+
547
+ # Convert linear experiment number to multi-dimensional indices
548
+ indices = []
549
+ remaining_exp_num = exp_num
550
+
551
+ for i in range(len(criteria_counts)):
552
+ # Calculate stride for this dimension
553
+ stride = 1
554
+ for j in range(i + 1, len(criteria_counts)):
555
+ stride *= criteria_counts[j]
556
+
557
+ # Calculate index for this dimension
558
+ dim_index = remaining_exp_num // stride
559
+ indices.append(dim_index)
560
+ remaining_exp_num = remaining_exp_num % stride
561
+
562
+ # Find the first criteria that has an n_agents method and use it
563
+ for i, criteria in enumerate(self.criteria):
564
+ if hasattr(criteria, "n_agents"):
565
+ return criteria.n_agents(indices[i])
566
+
567
+ # If no criteria has n_agents method, raise an error
568
+ raise AttributeError("No criteria has an 'n_agents' method")
569
+
570
+
571
+ def univar_factory(
572
+ main_config: types.YAMLDict,
573
+ cmdopts: types.Cmdopts,
574
+ batch_input_root: pathlib.Path,
575
+ cli_arg: str,
576
+ scenario,
577
+ ) -> BaseBatchCriteria:
679
578
  """
680
- Construct a batch criteria object from a single cmdline argument.
579
+ Construct a univariate batch criteria object from a single cmdline argument.
681
580
  """
682
- category = cli_arg.split('.')[0]
683
- path = f'variables.{category}'
581
+ category = cli_arg.split(".")[0]
582
+ path = f"variables.{category}"
684
583
 
685
584
  module = pm.bc_load(cmdopts, category)
686
585
  bcfactory = getattr(module, "factory")
687
586
 
688
- if 5 in cmdopts['pipeline']:
689
- ret = bcfactory(cli_arg,
690
- main_config,
691
- cmdopts,
692
- scenario=scenario)()
587
+ if 5 in cmdopts["pipeline"]:
588
+ ret = bcfactory(
589
+ cli_arg, main_config, cmdopts, batch_input_root, scenario=scenario
590
+ )
693
591
  else:
694
- ret = bcfactory(cli_arg, main_config, cmdopts)()
592
+ ret = bcfactory(cli_arg, main_config, cmdopts, batch_input_root)
695
593
 
696
- logging.info("Create univariate batch criteria '%s' from '%s'",
697
- ret.__class__.__name__,
698
- path)
594
+ logging.info("Create univariate batch criteria %s from %s", ret.name, path)
699
595
  return ret # type: ignore
700
596
 
701
597
 
702
- def __bivar_factory(main_config: types.YAMLDict,
703
- cmdopts: types.Cmdopts,
704
- cli_arg: tp.List[str],
705
- scenario: str) -> IConcreteBatchCriteria:
706
- criteria1 = __univar_factory(main_config, cmdopts, cli_arg[0], scenario)
707
- criteria2 = __univar_factory(main_config, cmdopts, cli_arg[1], scenario)
598
+ def factory(
599
+ main_config: types.YAMLDict,
600
+ cmdopts: types.Cmdopts,
601
+ batch_input_root: pathlib.Path,
602
+ args: argparse.Namespace,
603
+ scenario: tp.Optional[str] = None,
604
+ ) -> BaseBatchCriteria:
605
+ """
606
+ Construct a multivariate batch criteria object from cmdline input.
607
+ """
608
+ criterias = [
609
+ univar_factory(main_config, cmdopts, batch_input_root, arg, scenario)
610
+ for arg in args.batch_criteria
611
+ ]
708
612
 
709
613
  # Project hook
710
- bc = pm.module_load_tiered(project=cmdopts['project'],
711
- path='variables.batch_criteria')
712
- ret = bc.BivarBatchCriteria(criteria1, criteria2)
614
+ bc = pm.module_load_tiered(
615
+ project=cmdopts["project"], path="variables.batch_criteria"
616
+ )
617
+ ret = bc.XVarBatchCriteria(criterias)
713
618
 
714
- logging.info("Created bivariate batch criteria from %s,%s",
715
- ret.criteria1.__class__.__name__,
716
- ret.criteria2.__class__.__name__)
619
+ logging.info(
620
+ "Created %s-D batch criteria from %s",
621
+ len(criterias),
622
+ ",".join([c.name for c in criterias]),
623
+ )
717
624
 
718
625
  return ret # type: ignore
719
626
 
720
627
 
721
- __api__ = [
722
- 'BatchCriteria',
723
- 'IConcreteBatchCriteria',
724
- 'UnivarBatchCriteria',
725
- 'BivarBatchCriteria',
628
+ __all__ = [
629
+ "BaseBatchCriteria",
630
+ "UnivarBatchCriteria",
631
+ "XVarBatchCriteria",
726
632
  ]