sierra-research 1.3.11__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.11.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 -251
  191. sierra/core/graphs/stacked_surface_graph.py +0 -220
  192. sierra/core/graphs/summary_line_graph.py +0 -371
  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 -320
  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.11.data/data/share/man/man1/sierra-cli.1 +0 -2349
  244. sierra_research-1.3.11.data/data/share/man/man7/sierra-examples.7 +0 -508
  245. sierra_research-1.3.11.data/data/share/man/man7/sierra-exec-envs.7 +0 -331
  246. sierra_research-1.3.11.data/data/share/man/man7/sierra-glossary.7 +0 -285
  247. sierra_research-1.3.11.data/data/share/man/man7/sierra-platforms.7 +0 -358
  248. sierra_research-1.3.11.data/data/share/man/man7/sierra-usage.7 +0 -729
  249. sierra_research-1.3.11.data/data/share/man/man7/sierra.7 +0 -78
  250. sierra_research-1.3.11.dist-info/METADATA +0 -492
  251. sierra_research-1.3.11.dist-info/RECORD +0 -133
  252. sierra_research-1.3.11.dist-info/top_level.txt +0 -1
  253. {sierra_research-1.3.11.dist-info → sierra_research-1.5.0.dist-info}/entry_points.txt +0 -0
  254. {sierra_research-1.3.11.dist-info → sierra_research-1.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,159 @@
1
+ # Copyright 2021 John Harwell, All rights reserved.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+ """
6
+ Command line parsing and validation for the :term:`ROS1+robot` engine.
7
+ """
8
+
9
+ # Core packages
10
+ import typing as tp
11
+ import argparse
12
+
13
+ # 3rd party packages
14
+
15
+ # Project packages
16
+ from sierra.core import types, ros1, config
17
+ from sierra.plugins import PluginCmdline
18
+ from sierra.core.ros1 import cmdline
19
+
20
+
21
+ class EngineCmdline(cmdline.ROSCmdline):
22
+ """Defines :term:`ROS1` extensions to :class:`~sierra.core.cmdline.CoreCmdline`."""
23
+
24
+ def __init__(
25
+ self,
26
+ parents: tp.Optional[tp.List[argparse.ArgumentParser]],
27
+ stages: tp.List[int],
28
+ ) -> None:
29
+ super().__init__(parents, stages)
30
+
31
+ def init_multistage(self) -> None:
32
+ super().init_multistage()
33
+ self.multistage.add_argument(
34
+ "--skip-online-check",
35
+ help="""
36
+ If passed, then the usual 'is this robot online' checks will be
37
+ skipped.
38
+ """
39
+ + self.stage_usage_doc([1, 2]),
40
+ action="store_true",
41
+ )
42
+
43
+ self.multistage.add_argument(
44
+ "--online-check-method",
45
+ choices=["ping+ssh", "nc+ssh"],
46
+ help="""
47
+ How SIERRA should check if a given robot is online. Valid
48
+ values:
49
+
50
+ - ``ping+ssh`` - First, verify that you can ping each the
51
+ hostname/IP associated with each robot. Second, verify
52
+ that passwordless ssh to the hostname/IP works. This is
53
+ the most common option.
54
+
55
+ - ``nc+ssh`` - First, verify that an ssh connection exists
56
+ from the SIERRA host machine to the robot on the
57
+ specified port using netcat. Second, verify that
58
+ passwordless ssh to the robot on the specified port
59
+ works. This is useful when connecting to the robots
60
+ through a reverse SSH tunnel, which can be necessary if
61
+ the robots don't have a fixed IP address and cannot be
62
+ addressed by FQDN (looking at you eduroam...).
63
+ """,
64
+ default="ping+ssh",
65
+ )
66
+
67
+ def init_stage1(self) -> None:
68
+ super().init_stage1()
69
+ self.stage1.add_argument(
70
+ "--skip-sync",
71
+ help="""
72
+ If passed, then the generated experiment will not be synced to
73
+ robots. This is useful when:
74
+
75
+ - You are developing your :term:`Project` and just want to
76
+ check locally if the experiment is being generated
77
+ properly.
78
+
79
+ - You have a lot of robots and/or the network connection
80
+ from the SIERRA host machine to the robots is slow, and
81
+ copying the experiment multiple times as you tweak
82
+ parameters takes a long time.
83
+ """
84
+ + self.stage_usage_doc([1]),
85
+ action="store_true",
86
+ )
87
+
88
+ def init_stage2(self) -> None:
89
+ super().init_stage2()
90
+
91
+ self.stage2.add_argument(
92
+ "--exec-resume",
93
+ help="""
94
+ Resume a batch experiment that was killed/stopped/etc last time
95
+ SIERRA was run.
96
+ """
97
+ + self.stage_usage_doc([2]),
98
+ action="store_true",
99
+ default=False,
100
+ )
101
+
102
+ self.stage2.add_argument(
103
+ "--exec-inter-run-pause",
104
+ metavar="SECONDS",
105
+ help="""
106
+ How long to pause between :term:`Experimental Runs
107
+ <Experimental Run>`, giving you time to reset the environment,
108
+ move robots, etc.
109
+ """
110
+ + self.stage_usage_doc([2]),
111
+ type=int,
112
+ default=config.kROS["inter_run_pause"],
113
+ )
114
+
115
+
116
+ def build(
117
+ parents: tp.List[argparse.ArgumentParser], stages: tp.List[int]
118
+ ) -> PluginCmdline:
119
+ """
120
+ Get a cmdline parser supporting the :term:`ROS1+Robot` engine.
121
+
122
+ Extends built-in cmdline parser with:
123
+
124
+ - :class:`~sierra.core.ros1.cmdline.ROSCmdline` (ROS1 common)
125
+
126
+ - :class:`~sierra.plugins.engine.ros1robot.cmdline.EngineCmdline`
127
+ (ROS1+robot specifics)
128
+ """
129
+ return EngineCmdline(parents=parents, stages=stages)
130
+
131
+
132
+ def to_cmdopts(args: argparse.Namespace) -> types.Cmdopts:
133
+ """Update cmdopts with ROS1+robot-specific cmdline options."""
134
+ opts = ros1.cmdline.to_cmdopts(args)
135
+ self_updates = {
136
+ # Multistage
137
+ "exec_jobs_per_node": 1, # (1 job/robot)
138
+ "skip_online_check": args.skip_online_check,
139
+ "online_check_method": args.online_check_method,
140
+ # stage 1
141
+ "skip_sync": args.skip_sync,
142
+ # stage 2
143
+ "exec_resume": args.exec_resume,
144
+ "exec_inter_run_pause": args.exec_inter_run_pause,
145
+ }
146
+
147
+ opts |= self_updates
148
+ return opts
149
+
150
+
151
+ def sphinx_cmdline_stage1():
152
+ return EngineCmdline([], [1]).parser
153
+
154
+
155
+ def sphinx_cmdline_stage2():
156
+ return EngineCmdline([], [2]).parser
157
+
158
+
159
+ __all__ = ["EngineCmdline"]
@@ -1,7 +1,11 @@
1
1
  # Copyright 2022 John Harwell, All rights reserved.
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ """
5
+ Generators module for the :term:`ROS1+Robot` engine.
4
6
 
7
+ See :ref:`plugins/engine/ros1robot`.
8
+ """
5
9
  # Core packages
6
10
 
7
11
  # 3rd party packages
@@ -0,0 +1,95 @@
1
+ # Copyright 2021 John Harwell, All rights reserved.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """Classes for generating common XML modifications to :term:`ROS1` input files.
5
+
6
+ I.e., changes which are engine-specific, but applicable to all projects using
7
+ ROS with a real robot execution environment.
8
+
9
+ """
10
+ # Core packages
11
+ import logging
12
+ import pathlib
13
+
14
+ # 3rd party packages
15
+ import yaml
16
+
17
+ # Project packages
18
+ from sierra.core.experiment import definition
19
+ from sierra.core.experiment import spec as expspec
20
+ from sierra.core import types, ros1, config, utils
21
+
22
+ _logger = logging.getLogger(__name__)
23
+
24
+
25
+ def for_all_exp(
26
+ spec: expspec.ExperimentSpec,
27
+ controller: str,
28
+ cmdopts: types.Cmdopts,
29
+ expdef_template_fpath: pathlib.Path,
30
+ ) -> definition.BaseExpDef:
31
+ """Generate changes for all experiments in the batch."""
32
+ exp_def = ros1.generators.for_all_exp(
33
+ spec, controller, cmdopts, expdef_template_fpath
34
+ )
35
+
36
+ _logger.debug("Writing separate <master> launch file")
37
+ exp_def.write_config.add(
38
+ {
39
+ "src_parent": ".",
40
+ "src_tag": "master",
41
+ "opath_leaf": "_master" + config.kROS["launch_file_ext"],
42
+ "new_children": None,
43
+ "rename_to": "launch",
44
+ "new_children_parent": None,
45
+ }
46
+ )
47
+
48
+ # Add <robot> tag
49
+ if not exp_def.has_element("./robot"):
50
+ exp_def.element_add(".", "robot", {})
51
+ if not exp_def.has_element("./robot/group/[@ns='sierra']"):
52
+ exp_def.element_add("./robot", "group", {"ns": "sierra"})
53
+
54
+ return exp_def
55
+
56
+
57
+ def for_single_exp_run(
58
+ exp_def: definition.BaseExpDef,
59
+ run_num: int,
60
+ run_output_path: pathlib.Path,
61
+ launch_stem_path: pathlib.Path,
62
+ random_seed: int,
63
+ cmdopts: types.Cmdopts,
64
+ ) -> None:
65
+ """Generate changes for a single experimental run."""
66
+ ros1.generators.for_single_exp_run(
67
+ exp_def, run_num, run_output_path, launch_stem_path, random_seed, cmdopts
68
+ )
69
+
70
+ main_path = pathlib.Path(cmdopts["project_config_root"], config.kYAML.main)
71
+
72
+ with utils.utf8open(main_path) as f:
73
+ main_config = yaml.load(f, yaml.FullLoader)
74
+
75
+ n_agents = utils.get_n_agents(
76
+ main_config, cmdopts, launch_stem_path.parent, exp_def
77
+ )
78
+
79
+ for i in range(0, n_agents):
80
+ prefix = main_config["ros"]["robots"][cmdopts["robot"]]["prefix"]
81
+ exp_def.write_config.add(
82
+ {
83
+ "src_parent": "./robot",
84
+ "src_tag": f"group/[@ns='{prefix}{i}']",
85
+ "opath_leaf": f"_robot{i}" + config.kROS["launch_file_ext"],
86
+ "new_children": [definition.ElementAdd.as_root("launch", {})],
87
+ "new_children_parent": ".",
88
+ "rename_to": None,
89
+ "child_grafts_parent": ".",
90
+ "child_grafts": ["./robot/group/[@ns='sierra']"],
91
+ }
92
+ )
93
+
94
+
95
+ __all__ = ["for_all_exp", "for_single_exp_run"]
@@ -0,0 +1,410 @@
1
+ # Copyright 2021 John Harwell, All rights reserved.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """Plugin for :term:`ROS1`-based robot :term:`Engines <Engine>`."""
5
+ # Core packages
6
+ import argparse
7
+ import logging
8
+ import typing as tp
9
+ import os
10
+ import subprocess
11
+ import pwd
12
+ import pathlib
13
+
14
+ # 3rd party packages
15
+ import implements
16
+ import yaml
17
+
18
+ # Project packages
19
+ from sierra.core import engine, config, ros1, types, utils, batchroot, execenv
20
+ from sierra.core.experiment import bindings, definition
21
+ import sierra.core.variables.batch_criteria as bc
22
+
23
+
24
+ _logger = logging.getLogger("engine.ros1robot")
25
+
26
+
27
+ @implements.implements(bindings.IExpRunShellCmdsGenerator)
28
+ class ExpRunShellCmdsGenerator:
29
+ """Generate the commands to run a single :term:`Experimental Run`."""
30
+
31
+ def __init__(
32
+ self,
33
+ cmdopts: types.Cmdopts,
34
+ criteria: bc.XVarBatchCriteria,
35
+ n_agents: int,
36
+ exp_num: int,
37
+ ) -> None:
38
+ self.cmdopts = cmdopts
39
+ self.n_agents = n_agents
40
+ self.exp_num = exp_num
41
+ self.criteria = criteria
42
+
43
+ def pre_run_cmds(
44
+ self, host: str, input_fpath: pathlib.Path, run_num: int
45
+ ) -> tp.List[types.ShellCmdSpec]:
46
+
47
+ master_ip = engine.get_local_ip()
48
+ port = config.kROS["port_base"] + self.exp_num
49
+ master_uri = f"http://{master_ip}:{port}"
50
+
51
+ ros_master = types.ShellCmdSpec(
52
+ cmd=f"export ROS_MASTER_URI={master_uri};", shell=True, env=True, wait=True
53
+ )
54
+
55
+ if host == "master":
56
+ if self.cmdopts["no_master_node"]:
57
+ return []
58
+ else:
59
+ return [ros_master]
60
+
61
+ main_path = os.path.join(self.cmdopts["project_config_root"], config.kYAML.main)
62
+
63
+ main_config = yaml.load(utils.utf8open(main_path), yaml.FullLoader)
64
+
65
+ _logger.debug(
66
+ "Generating pre-exec cmds for run%s slaves: %d robots",
67
+ run_num,
68
+ self.n_agents,
69
+ )
70
+
71
+ script_yaml = main_config["ros"]["robots"][self.cmdopts["robot"]]
72
+ script_file = script_yaml.get("setup_script", "$HOME/.bashrc")
73
+
74
+ ros_setup = types.ShellCmdSpec(
75
+ cmd=f". {script_file};", shell=True, wait=True, env=True
76
+ )
77
+
78
+ return [ros_setup, ros_master]
79
+
80
+ def exec_run_cmds(
81
+ self, host: str, input_fpath: pathlib.Path, run_num: int
82
+ ) -> tp.List[types.ShellCmdSpec]:
83
+ if host == "master":
84
+ return self._exec_run_cmds_master(host, input_fpath, run_num)
85
+ else:
86
+ return self._exec_run_cmds_slave(host, input_fpath, run_num)
87
+
88
+ def _exec_run_cmds_master(
89
+ self, host: str, input_fpath: pathlib.Path, run_num: int
90
+ ) -> tp.List[types.ShellCmdSpec]:
91
+
92
+ if self.cmdopts["no_master_node"]:
93
+ return []
94
+
95
+ _logger.debug("Generating exec cmds for run%s master", run_num)
96
+
97
+ # ROS master node
98
+ exp_dirname = self.criteria.gen_exp_names()[self.exp_num]
99
+ exp_template_path = utils.exp_template_path(
100
+ self.cmdopts, self.criteria.batch_input_root, exp_dirname
101
+ )
102
+ cmd = "{0} --wait {1}_run{2}_master{3};"
103
+
104
+ cmd = cmd.format(
105
+ config.kROS["launch_cmd"],
106
+ str(exp_template_path),
107
+ run_num,
108
+ config.kROS["launch_file_ext"],
109
+ )
110
+
111
+ # --wait tells roslaunch to wait for the configured master to come up
112
+ # before launch the "master" code.
113
+ #
114
+ # 2022/02/28: -p (apparently) tells roslaunch not to CONNECT to a master
115
+ # at the specified ort, but to LAUNCH a new master at the specified
116
+ # port. This is not really documented well.
117
+
118
+ master_node = types.ShellCmdSpec(cmd=cmd, shell=True, wait=True)
119
+
120
+ return [master_node]
121
+
122
+ def _exec_run_cmds_slave(
123
+ self, host: str, input_fpath: pathlib.Path, run_num: int
124
+ ) -> tp.List[types.ShellCmdSpec]:
125
+
126
+ _logger.debug(
127
+ "Generating exec cmds for run%s slaves: %d robots", run_num, self.n_agents
128
+ )
129
+
130
+ nodes = execenv.parse_nodefile(self.cmdopts["nodefile"])
131
+
132
+ if len(nodes) < self.n_agents:
133
+ _logger.critical(
134
+ (
135
+ "Need %d hosts to correctly generate launch "
136
+ "cmds for run%s with %d robots; %d available"
137
+ ),
138
+ self.n_agents,
139
+ run_num,
140
+ self.n_agents,
141
+ len(nodes),
142
+ )
143
+
144
+ ret = [] # type: tp.List[types.ShellCmdSpec]
145
+ for i in range(0, self.n_agents):
146
+ # --wait tells roslaunch to wait for the configured master to
147
+ # come up before launch the robot code.
148
+ cmd = "{0} --wait {1}_robot{2}{3};"
149
+ cmd = cmd.format(
150
+ config.kROS["launch_cmd"],
151
+ input_fpath,
152
+ i,
153
+ config.kROS["launch_file_ext"],
154
+ )
155
+ ret.extend([types.ShellCmdSpec(cmd=cmd, shell=True, wait=True)])
156
+ return ret
157
+
158
+ def post_run_cmds(
159
+ self, host: str, run_output_root: pathlib.Path
160
+ ) -> tp.List[types.ShellCmdSpec]:
161
+ if host == "master":
162
+ return []
163
+ else:
164
+ return [
165
+ types.ShellCmdSpec(
166
+ # Can't use killall, because that returns non-zero if things
167
+ # are cleaned up nicely.
168
+ cmd="if pgrep roslaunch; then pkill roslaunch; fi;",
169
+ shell=True,
170
+ wait=True,
171
+ )
172
+ ]
173
+
174
+
175
+ @implements.implements(bindings.IExpShellCmdsGenerator)
176
+ class ExpShellCmdsGenerator:
177
+ """Generate the commands to run the :term:`Experiment` in stage 2."""
178
+
179
+ def __init__(self, cmdopts: types.Cmdopts, exp_num: int) -> None:
180
+ self.cmdopts = cmdopts
181
+ self.exp_num = exp_num
182
+
183
+ def pre_exp_cmds(self) -> tp.List[types.ShellCmdSpec]:
184
+ local_ip = engine.get_local_ip()
185
+ port = config.kROS["port_base"] + self.exp_num
186
+ master_uri = f"http://{local_ip}:{port}"
187
+
188
+ _logger.info("Using ROS_MASTER_URI=%s", master_uri)
189
+
190
+ return [
191
+ types.ShellCmdSpec(
192
+ # roscore will run on the SIERRA host machine.
193
+ cmd=f"export ROS_MASTER_URI={master_uri}",
194
+ shell=True,
195
+ env=True,
196
+ wait=True,
197
+ ),
198
+ types.ShellCmdSpec(
199
+ # Each exppperiment gets their own roscore. Because each roscore
200
+ # has a different port, this prevents any robots from
201
+ # pre-emptively starting the next experiment before the rest of
202
+ # the robots have finished the current one.
203
+ cmd=f"roscore -p {port} & ",
204
+ shell=True,
205
+ wait=False,
206
+ ),
207
+ ]
208
+
209
+ def exec_exp_cmds(self, exec_opts: types.StrDict) -> tp.List[types.ShellCmdSpec]:
210
+ return []
211
+
212
+ def post_exp_cmds(self) -> tp.List[types.ShellCmdSpec]:
213
+ # Cleanup roscore processes on the SIERRA host machine which are still
214
+ # active because they don't know how to clean up after themselves.
215
+ return [
216
+ types.ShellCmdSpec(cmd="killall rosmaster;", shell=True, wait=True),
217
+ types.ShellCmdSpec(cmd="killall roscore;", shell=True, wait=True),
218
+ types.ShellCmdSpec(cmd="killall rosout;", shell=True, wait=True),
219
+ ]
220
+
221
+
222
+ @implements.implements(bindings.IExpConfigurer)
223
+ class ExpConfigurer:
224
+ """High level experiment configuration for the engine.
225
+
226
+ - Relaxing some ssh checks.
227
+
228
+ - Syncing files to robots.
229
+ """
230
+
231
+ def __init__(self, cmdopts: types.Cmdopts) -> None:
232
+ self.cmdopts = cmdopts
233
+
234
+ def parallelism_paradigm(self) -> str:
235
+ return "per-run"
236
+
237
+ def for_exp_run(
238
+ self, exp_input_root: pathlib.Path, run_output_root: pathlib.Path
239
+ ) -> None:
240
+ pass
241
+
242
+ def for_exp(self, exp_input_root: pathlib.Path) -> None:
243
+ if self.cmdopts["skip_sync"]:
244
+ _logger.info("Skipping syncing experiment inputs -> robots")
245
+ return
246
+ else:
247
+ _logger.info("Syncing experiment inputs -> robots")
248
+
249
+ nodes = execenv.parse_nodefile(self.cmdopts["nodefile"])
250
+
251
+ # Use {parallel ssh, rsync} to push each experiment to all robots in
252
+ # parallel--takes O(M*N) operation and makes it O(N) more or less.
253
+ pssh_base = "parallel-ssh"
254
+ prsync_base = "parallel-rsync"
255
+
256
+ for node in nodes:
257
+ remote_login = node.login
258
+ remote_port = node.port
259
+ remote_hostname = node.hostname
260
+ current_username = pwd.getpwuid(os.getuid())[0]
261
+
262
+ if not self.cmdopts["skip_online_check"]:
263
+ execenv.check_connectivity(
264
+ self.cmdopts,
265
+ remote_login,
266
+ remote_hostname,
267
+ remote_port,
268
+ self.cmdopts["robot"],
269
+ )
270
+
271
+ pssh_base += f" -H {remote_login}@{remote_hostname}:{remote_port}"
272
+ prsync_base += f" -H {remote_login}@{remote_hostname}:{remote_port}"
273
+
274
+ # In case the user is different on the remote machine than this one,
275
+ # and the location of the generated experiment is under /home.
276
+ robot_input_root = str(exp_input_root).replace(current_username, remote_login)
277
+
278
+ mkdir_cmd = (
279
+ f"{pssh_base} "
280
+ f"-O StrictHostKeyChecking=no "
281
+ f"mkdir -p {robot_input_root}"
282
+ )
283
+
284
+ rsync_cmd = (
285
+ f"{prsync_base} "
286
+ f"-avz "
287
+ f"-O StrictHostKeyChecking=no "
288
+ f"{exp_input_root}/ "
289
+ f"{robot_input_root}/"
290
+ )
291
+ res = None
292
+ try:
293
+ _logger.trace("Running mkdir: %s", mkdir_cmd) # type: ignore
294
+ res = subprocess.run(
295
+ mkdir_cmd,
296
+ shell=True,
297
+ check=True,
298
+ stdout=subprocess.PIPE,
299
+ stderr=subprocess.PIPE,
300
+ )
301
+ _logger.trace("Running rsync: %s", rsync_cmd) # type: ignore
302
+ res = subprocess.run(
303
+ rsync_cmd,
304
+ shell=True,
305
+ check=True,
306
+ stdout=subprocess.PIPE,
307
+ stderr=subprocess.PIPE,
308
+ )
309
+ except subprocess.CalledProcessError:
310
+ _logger.fatal(
311
+ "Unable to sync %s with %s: stdout=%s,stderr=%s",
312
+ exp_input_root,
313
+ robot_input_root,
314
+ res.stdout.decode("utf-8") if res else None,
315
+ res.stderr.decode("utf-8") if res else None,
316
+ )
317
+ raise
318
+
319
+
320
+ def cmdline_postparse_configure(
321
+ env: str, args: argparse.Namespace
322
+ ) -> argparse.Namespace:
323
+ """
324
+ Configure cmdline args after parsing for the :term:`ROS1+Robot` engine.
325
+
326
+ Checks for:
327
+
328
+ - List of robot IPs via ``--nodefile`` or :envvar:`SIERRA_NODEFILE`.
329
+
330
+ """
331
+ if args.nodefile is None:
332
+ assert "SIERRA_NODEFILE" in os.environ, (
333
+ "Non-ros1robot environment detected: --nodefile not "
334
+ "passed and 'SIERRA_NODEFILE' not found"
335
+ )
336
+ args.nodefile = os.environ["SIERRA_NODEFILE"]
337
+
338
+ assert utils.path_exists(
339
+ args.nodefile
340
+ ), f"SIERRA_NODEFILE '{args.nodefile}' does not exist"
341
+
342
+ _logger.info("Using '%s' as robot hostnames file", args.nodefile)
343
+
344
+ assert not args.engine_vc, "Engine visual capture not supported on ros1robot"
345
+
346
+ return args
347
+
348
+
349
+ def execenv_check(cmdopts: types.Cmdopts) -> None:
350
+ """
351
+ Verify execution environment in stage 2 for :term:`ROS1+Robot`.
352
+
353
+ Check for:
354
+
355
+ - :envvar:`ROS_VERSION` is ROS1.
356
+
357
+ - :envvar:`ROS_DISTRO` is {kinetic/noetic}.
358
+
359
+ - :program:`gazebo` can be found and the version is supported.
360
+ """
361
+
362
+ keys = ["ROS_DISTRO", "ROS_VERSION"]
363
+
364
+ for k in keys:
365
+ assert k in os.environ, f"Non-ROS1+robot environment detected: '{k}' not found"
366
+
367
+ # Check ROS distro
368
+ assert os.environ["ROS_DISTRO"] in [
369
+ "kinetic",
370
+ "noetic",
371
+ ], "SIERRA only supports ROS1 kinetic,noetic"
372
+
373
+ # Check ROS version
374
+ assert (
375
+ os.environ["ROS_VERSION"] == "1"
376
+ ), "Wrong ROS version: This plugin is for ROS1"
377
+
378
+
379
+ def population_size_from_pickle(
380
+ adds_def: tp.Union[definition.AttrChangeSet, definition.ElementAddList],
381
+ main_config: types.YAMLDict,
382
+ cmdopts: types.Cmdopts,
383
+ ) -> int:
384
+ return ros1.callbacks.population_size_from_pickle(adds_def, main_config, cmdopts)
385
+
386
+
387
+ def population_size_from_def(
388
+ exp_def: definition.BaseExpDef, main_config: types.YAMLDict, cmdopts: types.Cmdopts
389
+ ) -> int:
390
+ return ros1.callbacks.population_size_from_def(exp_def, main_config, cmdopts)
391
+
392
+
393
+ def agent_prefix_extract(main_config: types.YAMLDict, cmdopts: types.Cmdopts) -> str:
394
+ return ros1.callbacks.robot_prefix_extract(main_config, cmdopts)
395
+
396
+
397
+ def pre_exp_diagnostics(
398
+ cmdopts: types.Cmdopts, pathset: batchroot.PathSet, logger: logging.Logger
399
+ ) -> None:
400
+ s = "batch_exp_root='%s',runs/exp=%s"
401
+ logger.info(s, pathset.root, cmdopts["n_runs"])
402
+
403
+
404
+ __all__ = [
405
+ "ExpRunShellCmdsGenerator",
406
+ "ExpShellCmdsGenerator",
407
+ "ExpConfigurer",
408
+ "cmdline_postparse_configure",
409
+ "execenv_check",
410
+ ]
@@ -1,6 +1,11 @@
1
1
  # Copyright 2021 John Harwell, All rights reserved.
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ """
5
+ Variables module for the :term:`ROS1+Robot` engine.
6
+
7
+ See :ref:`plugins/engine/ros1robot`.
8
+ """
4
9
 
5
10
  # Core packages
6
11