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
@@ -0,0 +1,386 @@
1
+ # Copyright 2024 John Harwell, All rights reserved.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """Plugin for parsing and manipulating template input files in XML format."""
5
+
6
+ # Core packages
7
+ import pathlib
8
+ import logging
9
+ import xml.etree.ElementTree as ET
10
+ import typing as tp
11
+
12
+ # 3rd party packages
13
+ import implements
14
+
15
+ # Project packages
16
+ from sierra.core.experiment import definition
17
+ from sierra.core import types
18
+
19
+
20
+ class Writer:
21
+ """Write the XML experiment to the filesystem according to configuration.
22
+
23
+ More than one file may be written, as specified.
24
+ """
25
+
26
+ def __init__(self, tree: ET.ElementTree) -> None:
27
+ self.tree = tree
28
+ self.root = tree.getroot()
29
+ self.logger = logging.getLogger(__name__)
30
+
31
+ def __call__(
32
+ self, write_config: definition.WriterConfig, base_opath: pathlib.Path
33
+ ) -> None:
34
+ for config in write_config.values:
35
+ self._write_with_config(base_opath, config)
36
+
37
+ def _write_with_config(
38
+ self, base_opath: tp.Union[pathlib.Path, str], config: dict
39
+ ) -> None:
40
+ tree, src_root, opath = self._prepare_tree(pathlib.Path(base_opath), config)
41
+
42
+ if tree is None:
43
+ self.logger.warning(
44
+ "Cannot write non-existent tree@'%s' to '%s'", src_root, opath
45
+ )
46
+ return
47
+
48
+ self.logger.trace("Write tree@%s to %s", src_root, opath) # type: ignore
49
+
50
+ # Renaming tree root is not required
51
+ if "rename_to" in config and config["rename_to"] is not None:
52
+ tree.tag = config["rename_to"]
53
+ self.logger.trace(
54
+ "Rename tree root -> %s", config["rename_to"] # type: ignore
55
+ )
56
+
57
+ # Adding new children not required
58
+ if all(
59
+ k in config and config[k] is not None
60
+ for k in ["new_children_parent", "new_children"]
61
+ ):
62
+ self._add_new_children(config, tree)
63
+
64
+ # Grafts are not required
65
+ if all(
66
+ k in config and config[k] is not None
67
+ for k in ["child_grafts_parent", "child_grafts"]
68
+ ):
69
+ self._add_grafts(config, tree)
70
+
71
+ to_write = ET.ElementTree(tree)
72
+
73
+ ET.indent(to_write.getroot(), space="\t", level=0)
74
+ ET.indent(to_write, space="\t", level=0)
75
+ to_write.write(opath, encoding="utf-8")
76
+
77
+ def _add_grafts(self, config: dict, tree: ET.Element) -> None:
78
+
79
+ graft_parent = tree.find(config["child_grafts_parent"])
80
+ assert graft_parent is not None, f"Bad parent '{graft_parent}' for grafting"
81
+
82
+ for g in config["child_grafts"]:
83
+ self.logger.trace(
84
+ "Graft tree@'%s' as child under '%s'", g, graft_parent # type: ignore
85
+ )
86
+ elt = self.root.find(g)
87
+ graft_parent.append(elt)
88
+
89
+ def _add_new_children(self, config: dict, tree: ET.ElementTree) -> None:
90
+ """Given the experiment definition, add new children as configured.
91
+
92
+ We operate on the whole definition in-situ, rather than creating a new
93
+ subtree with all the children because that is less error prone in terms
94
+ of grafting the new subtree back into the experiment definition.
95
+ """
96
+
97
+ parent = tree.find(config["new_children_parent"])
98
+
99
+ for spec in config["new_children"]:
100
+ if spec.as_root_elt:
101
+ # Special case: Adding children to an empty tree
102
+ tree = ET.Element(spec.path, spec.attr)
103
+ continue
104
+
105
+ elt = parent.find(spec.path)
106
+
107
+ assert elt is not None, (
108
+ f"Could not find parent '{spec.path}' of new child element '{spec.tag}' "
109
+ "to add"
110
+ )
111
+
112
+ ET.SubElement(elt, spec.tag, spec.attr)
113
+
114
+ self.logger.trace(
115
+ "Create child element '%s' under '%s'", # type: ignore
116
+ spec.tag,
117
+ spec.path,
118
+ )
119
+
120
+ def _prepare_tree(
121
+ self, base_opath: pathlib.Path, config: dict
122
+ ) -> tp.Tuple[tp.Optional[ET.Element], str, pathlib.Path]:
123
+ assert "src_parent" in config, "'src_parent' key is required"
124
+ assert (
125
+ "src_tag" in config and config["src_tag"] is not None
126
+ ), "'src_tag' key is required"
127
+
128
+ if config["src_parent"] is None:
129
+ src_root = config["src_tag"]
130
+ else:
131
+ src_root = "{0}/{1}".format(config["src_parent"], config["src_tag"])
132
+
133
+ tree_out = self.tree.getroot().find(src_root)
134
+
135
+ # Customizing the output write path is not required
136
+ opath = base_opath
137
+ if "opath_leaf" in config and config["opath_leaf"] is not None:
138
+ opath = base_opath.with_name(base_opath.name + str(config["opath_leaf"]))
139
+
140
+ self.logger.trace(
141
+ "Preparing subtree write of '%s' to '%s', root='%s'",
142
+ tree_out,
143
+ opath,
144
+ tree_out,
145
+ )
146
+
147
+ return (tree_out, src_root, opath)
148
+
149
+
150
+ def root_querypath() -> str:
151
+ return "."
152
+
153
+
154
+ @implements.implements(definition.BaseExpDef)
155
+ class ExpDef:
156
+ """Read, write, and modify parsed XML files into experiment definitions."""
157
+
158
+ def __init__(
159
+ self,
160
+ input_fpath: pathlib.Path,
161
+ write_config: tp.Optional[definition.WriterConfig] = None,
162
+ ) -> None:
163
+
164
+ self.write_config = write_config
165
+ self.input_fpath = input_fpath
166
+ self.tree = ET.parse(self.input_fpath)
167
+ self.root = self.tree.getroot()
168
+ self.element_adds = definition.ElementAddList()
169
+ self.attr_chgs = definition.AttrChangeSet()
170
+
171
+ self.logger = logging.getLogger(__name__)
172
+
173
+ def n_mods(self) -> tp.Tuple[int, int]:
174
+ return len(self.element_adds), len(self.attr_chgs)
175
+
176
+ def write_config_set(self, config: definition.WriterConfig) -> None:
177
+ """Set the write config for the object.
178
+
179
+ Provided for cases in which the configuration is dependent on whether or
180
+ not certain tags/element are present in the input file.
181
+
182
+ """
183
+ self.write_config = config
184
+
185
+ def write(self, base_opath: pathlib.Path) -> None:
186
+ assert self.write_config is not None, "Can't write without write config"
187
+
188
+ writer = Writer(self.tree)
189
+ writer(self.write_config, base_opath)
190
+
191
+ def flatten(self, keys: tp.List[str]) -> None:
192
+ raise NotImplementedError("The XML expdef plugin does not support flattening")
193
+
194
+ def attr_get(self, path: str, attr: str) -> tp.Optional[tp.Union[str, int, float]]:
195
+ el = self.root.find(path)
196
+ if el is not None and attr in el.attrib:
197
+ return el.attrib[attr]
198
+ return None
199
+
200
+ def attr_change(
201
+ self,
202
+ path: str,
203
+ attr: str,
204
+ value: tp.Union[str, int, float],
205
+ noprint: bool = False,
206
+ ) -> bool:
207
+ el = self.root.find(path)
208
+ if el is None:
209
+ if not noprint:
210
+ self.logger.warning("Parent element '%s' not found", path)
211
+ return False
212
+
213
+ if attr not in el.attrib:
214
+ if not noprint:
215
+ self.logger.warning("Attribute '%s' not found in path '%s'", attr, path)
216
+ return False
217
+
218
+ el.attrib[attr] = value
219
+ self.logger.trace(
220
+ "Modify attr: '%s/%s' = '%s'", path, attr, value # type: ignore
221
+ )
222
+
223
+ self.attr_chgs.add(definition.AttrChange(path, attr, str(value)))
224
+ return True
225
+
226
+ def attr_add(
227
+ self,
228
+ path: str,
229
+ attr: str,
230
+ value: tp.Union[str, int, float],
231
+ noprint: bool = False,
232
+ ) -> bool:
233
+ el = self.root.find(path)
234
+ if el is None:
235
+ if not noprint:
236
+ self.logger.warning("Parent element '%s' not found", path)
237
+ return False
238
+
239
+ if attr in el.attrib:
240
+ if not noprint:
241
+ self.logger.warning("Attribute '%s' already in path '%s'", attr, path)
242
+ return False
243
+
244
+ el.set(attr, value)
245
+ self.logger.trace(
246
+ "Add new attribute: '%s/%s' = '%s'", path, attr, value # type: ignore
247
+ )
248
+ self.attr_chgs.add(definition.AttrChange(path, attr, str(value)))
249
+ return True
250
+
251
+ def has_element(self, path: str) -> bool:
252
+ return self.root.find(path) is not None
253
+
254
+ def has_attr(self, path: str, attr: str) -> bool:
255
+ el = self.root.find(path)
256
+ if el is None:
257
+ return False
258
+ return attr in el.attrib
259
+
260
+ def element_change(self, path: str, tag: str, value: str) -> bool:
261
+ el = self.root.find(path)
262
+ if el is None:
263
+ self.logger.warning("Parent element '%s' not found", path)
264
+ return False
265
+
266
+ for child in el:
267
+ if child.tag == tag:
268
+ child.tag = value
269
+ self.logger.trace(
270
+ "Modify element: '%s/%s' = '%s'", path, tag, value # type: ignore
271
+ )
272
+ return True
273
+
274
+ self.logger.warning("No such element '%s' found in '%s'", tag, path)
275
+ return False
276
+
277
+ def element_remove(self, path: str, tag: str, noprint: bool = False) -> bool:
278
+ parent = self.root.find(path)
279
+
280
+ if parent is None:
281
+ if not noprint:
282
+ self.logger.warning("Parent node '%s' not found", path)
283
+ return False
284
+
285
+ victim = parent.find(tag)
286
+ if victim is None:
287
+ if not noprint:
288
+ self.logger.warning("No victim '%s' found in parent '%s'", tag, path)
289
+ return False
290
+
291
+ parent.remove(victim)
292
+ return True
293
+
294
+ def element_remove_all(self, path: str, tag: str, noprint: bool = False) -> bool:
295
+
296
+ parent = self.root.find(path)
297
+
298
+ if parent is None:
299
+ if not noprint:
300
+ self.logger.warning("Parent element '%s' not found", path)
301
+ return False
302
+
303
+ victims = parent.findall(tag)
304
+ if not victims:
305
+ if not noprint:
306
+ self.logger.warning(
307
+ "No victims matching '%s' found in parent '%s'", tag, path
308
+ )
309
+ return False
310
+
311
+ for victim in victims:
312
+ parent.remove(victim)
313
+ self.logger.trace(
314
+ "Remove matching element: '%s/%s'", path, tag # type: ignore
315
+ )
316
+
317
+ return True
318
+
319
+ def element_add(
320
+ self,
321
+ path: str,
322
+ tag: str,
323
+ attr: tp.Optional[types.StrDict] = None,
324
+ allow_dup: bool = True,
325
+ noprint: bool = False,
326
+ ) -> bool:
327
+ """
328
+ Add tag name as a child element of enclosing parent.
329
+ """
330
+ parent = self.root.find(path)
331
+
332
+ if parent is None:
333
+ if not noprint:
334
+ self.logger.warning("Parent element '%s' not found", path)
335
+ return False
336
+
337
+ if not allow_dup:
338
+ if parent.find(tag) is not None:
339
+ if not noprint:
340
+ self.logger.warning(
341
+ "Child element '%s' already in parent '%s'", tag, path
342
+ )
343
+ return False
344
+
345
+ ET.SubElement(parent, tag, attrib=attr if attr else {})
346
+ self.logger.trace(
347
+ "Add new unique element: '%s/%s' = '%s'", # type: ignore
348
+ path,
349
+ tag,
350
+ str(attr),
351
+ )
352
+ else:
353
+ # Use ET.Element instead of ET.SubElement so that child nodes with
354
+ # the same 'tag' don't overwrite each other.
355
+ child = ET.Element(tag, attrib=attr if attr else {})
356
+ parent.append(child)
357
+ self.logger.trace(
358
+ "Add new element: '%s/%s' = '%s'", path, tag, str(attr) # type: ignore
359
+ )
360
+
361
+ self.element_adds.append(definition.ElementAdd(path, tag, attr, allow_dup))
362
+ return True
363
+
364
+
365
+ def unpickle(
366
+ fpath: pathlib.Path,
367
+ ) -> tp.Optional[tp.Union[definition.AttrChangeSet, definition.ElementAddList]]:
368
+ """Unickle all XML modifications from the pickle file at the path.
369
+
370
+ You don't know how many there are, so go until you get an exception.
371
+
372
+ """
373
+ try:
374
+ return definition.AttrChangeSet.unpickle(fpath)
375
+ except (EOFError, TypeError):
376
+ pass
377
+
378
+ try:
379
+ return definition.ElementAddList.unpickle(fpath)
380
+ except EOFError:
381
+ pass
382
+
383
+ raise NotImplementedError
384
+
385
+
386
+ __all__ = ["ExpDef", "unpickle"]
@@ -1,10 +1,10 @@
1
1
  # Copyright 2021 John Harwell, All rights reserved.
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ """Common functionality ``--proc`` plugins."""
4
5
 
5
6
  # Core packages
6
7
 
7
8
  # 3rd party packages
8
9
 
9
10
  # Project packages
10
- from . import cmdline
@@ -0,0 +1,15 @@
1
+ #
2
+ # Copyright 2025 John Harwell, All rights reserved.
3
+ #
4
+ # SPDX-License Identifier: MIT
5
+ #
6
+ """Container module for the :term:`Data Collation` processing plugin."""
7
+ # Core packages
8
+
9
+ # 3rd party packages
10
+
11
+ # Project packages
12
+
13
+
14
+ def sierra_plugin_type() -> str:
15
+ return "pipeline"
@@ -0,0 +1,47 @@
1
+ #
2
+ # Copyright 2025 John Harwell, All rights reserved.
3
+ #
4
+ # SPDX-License Identifier: MIT
5
+ #
6
+
7
+ # Core packages
8
+ import typing as tp
9
+ import argparse
10
+
11
+ # 3rd party packages
12
+
13
+ # Project packages
14
+ from sierra.core import types
15
+ from sierra.plugins import PluginCmdline
16
+
17
+
18
+ def build(
19
+ parents: tp.List[argparse.ArgumentParser], stages: tp.List[int]
20
+ ) -> PluginCmdline:
21
+ """
22
+ Get a cmdline parser supporting the ``proc.collate`` processing plugin.
23
+ """
24
+ cmdline = PluginCmdline(parents, stages)
25
+ cmdline.multistage.add_argument(
26
+ "--skip-collate",
27
+ help="""
28
+ Specify that no collation of data across experiments within a batch
29
+ (stage 4) or across runs within an experiment (stage 3) should be
30
+ performed. Useful if collation takes a long time and multiple
31
+ types of stage 4 outputs are desired. Collation is generally
32
+ idempotent unless you change the stage3 options (YMMV).
33
+ """
34
+ + cmdline.stage_usage_doc([3, 4]),
35
+ action="store_true",
36
+ )
37
+ return cmdline
38
+
39
+
40
+ def to_cmdopts(args: argparse.Namespace) -> types.Cmdopts:
41
+ return {
42
+ "skip_collate": args.skip_collate,
43
+ }
44
+
45
+
46
+ def sphinx_cmdline_multistage():
47
+ return build([], [3, 4]).parser