physioblocks 1.0.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 (93) hide show
  1. physioblocks/__init__.py +37 -0
  2. physioblocks/base/__init__.py +27 -0
  3. physioblocks/base/operators.py +176 -0
  4. physioblocks/base/registers.py +108 -0
  5. physioblocks/computing/__init__.py +47 -0
  6. physioblocks/computing/assembling.py +291 -0
  7. physioblocks/computing/models.py +811 -0
  8. physioblocks/computing/quantities.py +354 -0
  9. physioblocks/configuration/__init__.py +38 -0
  10. physioblocks/configuration/aliases.py +203 -0
  11. physioblocks/configuration/base.py +123 -0
  12. physioblocks/configuration/computing/__init__.py +27 -0
  13. physioblocks/configuration/computing/quantities.py +56 -0
  14. physioblocks/configuration/constants.py +121 -0
  15. physioblocks/configuration/description/__init__.py +33 -0
  16. physioblocks/configuration/description/blocks.py +239 -0
  17. physioblocks/configuration/description/nets.py +155 -0
  18. physioblocks/configuration/functions.py +695 -0
  19. physioblocks/configuration/simulation/__init__.py +32 -0
  20. physioblocks/configuration/simulation/simulations.py +280 -0
  21. physioblocks/description/__init__.py +34 -0
  22. physioblocks/description/blocks.py +418 -0
  23. physioblocks/description/flux.py +157 -0
  24. physioblocks/description/nets.py +746 -0
  25. physioblocks/io/__init__.py +29 -0
  26. physioblocks/io/aliases.py +73 -0
  27. physioblocks/io/configuration.py +125 -0
  28. physioblocks/launcher/__main__.py +285 -0
  29. physioblocks/launcher/configuration.py +231 -0
  30. physioblocks/launcher/configure/__main__.py +99 -0
  31. physioblocks/launcher/constants.py +105 -0
  32. physioblocks/launcher/files.py +150 -0
  33. physioblocks/launcher/series.py +165 -0
  34. physioblocks/library/__init__.py +27 -0
  35. physioblocks/library/aliases/blocks/c_block.json +5 -0
  36. physioblocks/library/aliases/blocks/rc_block.json +5 -0
  37. physioblocks/library/aliases/blocks/rcr_block.json +5 -0
  38. physioblocks/library/aliases/blocks/spherical_cavity_block.json +5 -0
  39. physioblocks/library/aliases/blocks/valve_rl_block.json +5 -0
  40. physioblocks/library/aliases/flux/heart_flux_dof_couples.jsonc +4 -0
  41. physioblocks/library/aliases/model_components/active_law_macro_huxley_two_moments.json +5 -0
  42. physioblocks/library/aliases/model_components/rheology_fiber_additive.json +5 -0
  43. physioblocks/library/aliases/model_components/spherical_dynamics.json +5 -0
  44. physioblocks/library/aliases/model_components/velocity_law_hht.json +5 -0
  45. physioblocks/library/aliases/nets/circulation_alone_net.json +31 -0
  46. physioblocks/library/aliases/nets/spherical_heart_net.json +93 -0
  47. physioblocks/library/aliases/simulations/circulation_alone_forward_simulation.jsonc +55 -0
  48. physioblocks/library/aliases/simulations/default_forward_simulation.jsonc +7 -0
  49. physioblocks/library/aliases/simulations/default_time.jsonc +8 -0
  50. physioblocks/library/aliases/simulations/newton_method_solver.json +5 -0
  51. physioblocks/library/aliases/simulations/spherical_heart_forward_simulation.jsonc +157 -0
  52. physioblocks/library/aliases/simulations/spherical_heart_with_respiration_forward_simulation.jsonc +45 -0
  53. physioblocks/library/blocks/__init__.py +27 -0
  54. physioblocks/library/blocks/capacitances.py +516 -0
  55. physioblocks/library/blocks/cavity.py +192 -0
  56. physioblocks/library/blocks/valves.py +281 -0
  57. physioblocks/library/functions/__init__.py +27 -0
  58. physioblocks/library/functions/base_operations.py +129 -0
  59. physioblocks/library/functions/first_order.py +113 -0
  60. physioblocks/library/functions/piecewise.py +271 -0
  61. physioblocks/library/functions/trigonometric.py +78 -0
  62. physioblocks/library/functions/watchers.py +113 -0
  63. physioblocks/library/model_components/__init__.py +27 -0
  64. physioblocks/library/model_components/active_law.py +345 -0
  65. physioblocks/library/model_components/dynamics.py +986 -0
  66. physioblocks/library/model_components/rheology.py +160 -0
  67. physioblocks/library/model_components/velocity_law.py +169 -0
  68. physioblocks/references/circulation_alone_sim.jsonc +24 -0
  69. physioblocks/references/spherical_heart_respiration_sim.jsonc +33 -0
  70. physioblocks/references/spherical_heart_sim.jsonc +29 -0
  71. physioblocks/registers/__init__.py +32 -0
  72. physioblocks/registers/load_function_register.py +93 -0
  73. physioblocks/registers/save_function_register.py +106 -0
  74. physioblocks/registers/type_register.py +97 -0
  75. physioblocks/simulation/__init__.py +48 -0
  76. physioblocks/simulation/constants.py +30 -0
  77. physioblocks/simulation/functions.py +71 -0
  78. physioblocks/simulation/runtime.py +484 -0
  79. physioblocks/simulation/saved_quantities.py +129 -0
  80. physioblocks/simulation/setup.py +576 -0
  81. physioblocks/simulation/solvers.py +235 -0
  82. physioblocks/simulation/state.py +340 -0
  83. physioblocks/simulation/time_manager.py +354 -0
  84. physioblocks/utils/__init__.py +27 -0
  85. physioblocks/utils/dynamic_import_utils.py +150 -0
  86. physioblocks/utils/exceptions_utils.py +115 -0
  87. physioblocks/utils/gradient_test_utils.py +337 -0
  88. physioblocks/utils/math_utils.py +109 -0
  89. physioblocks-1.0.0.dist-info/METADATA +127 -0
  90. physioblocks-1.0.0.dist-info/RECORD +93 -0
  91. physioblocks-1.0.0.dist-info/WHEEL +4 -0
  92. physioblocks-1.0.0.dist-info/licenses/licenses/GPL-3.0-only.txt +674 -0
  93. physioblocks-1.0.0.dist-info/licenses/licenses/LGPL-3.0-only.txt +165 -0
@@ -0,0 +1,29 @@
1
+ # SPDX-FileCopyrightText: Copyright INRIA
2
+ #
3
+ # SPDX-License-Identifier: LGPL-3.0-only
4
+ #
5
+ # Copyright INRIA
6
+ #
7
+ # This file is part of PhysioBlocks, a library mostly developed by the
8
+ # [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9
+ #
10
+ # Authors:
11
+ # - Colin Drieu
12
+ # - Dominique Chapelle
13
+ # - François Kimmig
14
+ # - Philippe Moireau
15
+ #
16
+ # PhysioBlocks is free software: you can redistribute it and/or modify it under the
17
+ # terms of the GNU Lesser General Public License as published by the Free Software
18
+ # Foundation, version 3 of the License.
19
+ #
20
+ # PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22
+ # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License along with
25
+ # PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
26
+
27
+ """
28
+ Defines read and write files functions
29
+ """
@@ -0,0 +1,73 @@
1
+ # SPDX-FileCopyrightText: Copyright INRIA
2
+ #
3
+ # SPDX-License-Identifier: LGPL-3.0-only
4
+ #
5
+ # Copyright INRIA
6
+ #
7
+ # This file is part of PhysioBlocks, a library mostly developed by the
8
+ # [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9
+ #
10
+ # Authors:
11
+ # - Colin Drieu
12
+ # - Dominique Chapelle
13
+ # - François Kimmig
14
+ # - Philippe Moireau
15
+ #
16
+ # PhysioBlocks is free software: you can redistribute it and/or modify it under the
17
+ # terms of the GNU Lesser General Public License as published by the Free Software
18
+ # Foundation, version 3 of the License.
19
+ #
20
+ # PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22
+ # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License along with
25
+ # PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
26
+
27
+ """Defines methods to load aliases folders."""
28
+
29
+ from pathlib import Path
30
+
31
+ from physioblocks.configuration.aliases import add_alias
32
+ from physioblocks.io.configuration import read_json
33
+
34
+
35
+ def load_aliases(path: str) -> None:
36
+ """
37
+ Load all aliases recursively in the directory into the **Alias Register**.
38
+
39
+ .. warning::
40
+
41
+ The given key for each alias is its file name without extensions.
42
+ It has to be unique.
43
+
44
+ :param path: the alias directory path to load.
45
+ :type path: Path
46
+ """
47
+ directory_path = Path(path)
48
+
49
+ if directory_path.is_dir() is False:
50
+ raise OSError(
51
+ str.format(
52
+ "Provided alias directory is not a folder: {0}",
53
+ directory_path,
54
+ )
55
+ )
56
+
57
+ if directory_path.exists() is False:
58
+ raise OSError(
59
+ str.format(
60
+ "Provided alias directory do not exist: {0}",
61
+ directory_path,
62
+ )
63
+ )
64
+
65
+ for child in directory_path.iterdir():
66
+ if child.is_dir():
67
+ # recursivly load child directories
68
+ load_aliases(str(child))
69
+ else:
70
+ config_alias = read_json(str(child))
71
+ # get file name without extension as alias id
72
+ alias_id = child.name.removesuffix(child.suffix)
73
+ add_alias(alias_id, config_alias)
@@ -0,0 +1,125 @@
1
+ # SPDX-FileCopyrightText: Copyright INRIA
2
+ #
3
+ # SPDX-License-Identifier: LGPL-3.0-only
4
+ #
5
+ # Copyright INRIA
6
+ #
7
+ # This file is part of PhysioBlocks, a library mostly developed by the
8
+ # [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9
+ #
10
+ # Authors:
11
+ # - Colin Drieu
12
+ # - Dominique Chapelle
13
+ # - François Kimmig
14
+ # - Philippe Moireau
15
+ #
16
+ # PhysioBlocks is free software: you can redistribute it and/or modify it under the
17
+ # terms of the GNU Lesser General Public License as published by the Free Software
18
+ # Foundation, version 3 of the License.
19
+ #
20
+ # PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22
+ # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License along with
25
+ # PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
26
+
27
+ """
28
+ Defines methods to save and load
29
+ :class:`~physioblocks.configuration.base.Configuration` objects to a json file
30
+
31
+ .. note::
32
+
33
+ It is possible to save and load ``.jsonc`` files and use ``//`` characters to
34
+ comment the file.
35
+
36
+ The comments are discarded when the file is loaded: there are only here to increase
37
+ the json readability.
38
+
39
+ """
40
+
41
+ import json
42
+ import os
43
+ from pathlib import Path
44
+ from typing import Any
45
+
46
+ from physioblocks.configuration.base import Configuration
47
+
48
+ # Key giving the type of the configured object
49
+ _ITEM_TYPE_LABEL = "type"
50
+
51
+ # Character delimiting a comment
52
+ _COMMENT_CHAR = "//"
53
+
54
+
55
+ class _JSONConfigEncoder(json.JSONEncoder):
56
+ """
57
+ Derive from the base JSONEncoder class to redefine the
58
+ encoding of Configuration and Configuration objects
59
+ """
60
+
61
+ def default(self, obj: Any) -> Any:
62
+ """
63
+ Overwrite the default encoder method.
64
+
65
+ :param obj: the object to encode
66
+ :type obj: Any
67
+
68
+ :return: the encoded object
69
+ :rtype: dict[str, obj]
70
+ """
71
+
72
+ if isinstance(obj, Configuration):
73
+ item_dict: dict[str, Any]
74
+ item_dict = {}
75
+ item_dict[_ITEM_TYPE_LABEL] = obj.label
76
+ item_dict.update(obj.configuration_items)
77
+
78
+ return item_dict
79
+
80
+ return super().default(obj)
81
+
82
+
83
+ def write_json(file_path: str, config: Configuration) -> None:
84
+ """
85
+ Write the :class:`~physioblocks.configuration.base.Configuration` object
86
+ to a json file
87
+
88
+ :param file_path: the path of the file to read
89
+ :type file_path: str
90
+
91
+ :param config: the configuration to write
92
+ :type config: Configuration
93
+ """
94
+ config_json = json.dumps(config, cls=_JSONConfigEncoder, indent=4)
95
+ Path(file_path).write_text(config_json)
96
+
97
+
98
+ def read_json(file_path: str) -> Any:
99
+ """
100
+ Read a :class:`~physioblocks.configuration.base.Configuration` from a json file
101
+
102
+ :param file_path: the path of the file to read
103
+ :type file_path: str
104
+
105
+ :return: the loaded configuration
106
+ :rtype: Configuration
107
+ """
108
+ json_txt = Path(file_path).read_text()
109
+
110
+ uncommented_lines = [
111
+ line.split(_COMMENT_CHAR, 1)[0] for line in json_txt.splitlines()
112
+ ]
113
+ uncommented_json_txt = os.linesep.join(uncommented_lines)
114
+ return json.loads(uncommented_json_txt, object_hook=_as_config)
115
+
116
+
117
+ def _as_config(dict_obj: dict[Any, Any]) -> Any:
118
+ if _ITEM_TYPE_LABEL in dict_obj:
119
+ config_item = Configuration(
120
+ dict_obj[_ITEM_TYPE_LABEL],
121
+ {key: value for key, value in dict_obj.items() if key != _ITEM_TYPE_LABEL},
122
+ )
123
+ return config_item
124
+
125
+ return dict_obj
@@ -0,0 +1,285 @@
1
+ # SPDX-FileCopyrightText: Copyright INRIA
2
+ #
3
+ # SPDX-License-Identifier: LGPL-3.0-only
4
+ #
5
+ # Copyright INRIA
6
+ #
7
+ # This file is part of PhysioBlocks, a library mostly developed by the
8
+ # [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9
+ #
10
+ # Authors:
11
+ # - Colin Drieu
12
+ # - Dominique Chapelle
13
+ # - François Kimmig
14
+ # - Philippe Moireau
15
+ #
16
+ # PhysioBlocks is free software: you can redistribute it and/or modify it under the
17
+ # terms of the GNU Lesser General Public License as published by the Free Software
18
+ # Foundation, version 3 of the License.
19
+ #
20
+ # PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22
+ # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License along with
25
+ # PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
26
+
27
+ """
28
+ Launch a simulation from a simulation configuration file and organize
29
+ the results in a simulation folder.
30
+ """
31
+
32
+ import argparse
33
+ import logging
34
+ import site
35
+ import sys
36
+ from pathlib import Path
37
+ from typing import Any
38
+
39
+ import pandas as pd
40
+
41
+ import physioblocks.utils.exceptions_utils as exception_utils
42
+ from physioblocks.configuration import Configuration, load, unwrap_aliases
43
+ from physioblocks.io.configuration import read_json, write_json
44
+ from physioblocks.launcher.configuration import (
45
+ check_launcher_directory,
46
+ create_simulation_folder_path,
47
+ get_launcher_configuration,
48
+ import_configured_aliases,
49
+ import_configured_libraries,
50
+ )
51
+ from physioblocks.launcher.constants import (
52
+ LAUNCHER_COMPARE_TRACE_FILE_NAME,
53
+ LAUNCHER_SERIES_DIR_NAME,
54
+ )
55
+ from physioblocks.launcher.files import (
56
+ write_figure,
57
+ write_simulation_log_entry,
58
+ write_simulation_results,
59
+ )
60
+ from physioblocks.launcher.series import get_simulation_info
61
+ from physioblocks.simulation import AbstractSimulation, SimulationError
62
+
63
+ """
64
+ .. note:: When deleting a serie from the launcher folder, or a specific simulation from
65
+ a serie, the launchers logs are updated the next time any simulation is launched.
66
+ """
67
+
68
+ SIMULATION_LOG_FORMATER = logging.Formatter(logging.BASIC_FORMAT)
69
+ _root_logger = logging.getLogger()
70
+ _root_logger.setLevel(logging.DEBUG)
71
+
72
+ # register a hook to log uncaught exceptions
73
+ sys.excepthook = exception_utils.create_uncaught_exception_logger_handler(_root_logger)
74
+
75
+
76
+ def load_configuration(config_file_path: Path) -> Any:
77
+ simulation_config = read_json(str(config_file_path))
78
+ return unwrap_aliases(simulation_config)
79
+
80
+
81
+ def run_simulation(config: Configuration) -> pd.DataFrame:
82
+ simulation: AbstractSimulation = load(config)
83
+ try:
84
+ results = simulation.run()
85
+ _root_logger.info("Simulation complete.")
86
+ except SimulationError as sim_error:
87
+ _root_logger.error(sim_error)
88
+ results = sim_error.intermediate_results
89
+
90
+ return pd.DataFrame(results)
91
+
92
+
93
+ def add_log_handler(handler: logging.Handler, level: str | int) -> None:
94
+ handler.setFormatter(SIMULATION_LOG_FORMATER)
95
+ handler.setLevel(level)
96
+ _root_logger.addHandler(handler)
97
+
98
+
99
+ def main(
100
+ root_sim_directory: Path,
101
+ config_file_path: Path,
102
+ series: str,
103
+ message: str,
104
+ extension: str,
105
+ trace: bool = False,
106
+ reference_file_path: Path | None = None,
107
+ rows_height: float = 200.0,
108
+ ) -> int:
109
+ if check_launcher_directory(root_sim_directory) is False:
110
+ _root_logger.error(
111
+ str.format(
112
+ "{0} is not a suitable directory for the "
113
+ "launcher. Use the configure script to setup a new launcher directory.",
114
+ str(root_sim_directory.absolute()),
115
+ )
116
+ )
117
+ return -1
118
+
119
+ # Add site base libraries
120
+ site.addsitedir(str(root_sim_directory.absolute()))
121
+
122
+ # Prepare the series directory if necessary
123
+ serie_path = root_sim_directory / LAUNCHER_SERIES_DIR_NAME / series
124
+ if serie_path.exists() is False:
125
+ serie_path.mkdir(parents=True)
126
+
127
+ # Write the simulation log
128
+ sim_info = get_simulation_info(serie_path, message)
129
+ write_simulation_log_entry(root_sim_directory, sim_info)
130
+ sim_folder = create_simulation_folder_path(serie_path, sim_info)
131
+
132
+ # configure the simulation log file (always in DEBUG)
133
+ log_file_path = sim_folder / str.join(".", [sim_info.reference, "log"])
134
+ file_handler = logging.FileHandler(log_file_path)
135
+ add_log_handler(file_handler, logging.DEBUG)
136
+
137
+ # log the current simulation infos
138
+ _root_logger.info(str(sim_info))
139
+
140
+ launcher_configuration = get_launcher_configuration(root_sim_directory)
141
+ import_configured_libraries(launcher_configuration)
142
+ import_configured_aliases(launcher_configuration)
143
+
144
+ # Load configuration and unwrap aliases
145
+ sim_config = load_configuration(config_file_path)
146
+
147
+ # copy the unwrapped simulation configuration file to the simulation folder
148
+ write_json(str(sim_folder / config_file_path.name), sim_config)
149
+
150
+ # run the simulation
151
+ data = run_simulation(sim_config)
152
+
153
+ # write the result
154
+ write_simulation_results(sim_folder, sim_info, data, extension)
155
+
156
+ # trace the simulation result if needed.
157
+ if trace is True:
158
+ data = data.set_index("time")
159
+ write_figure(
160
+ data, sim_folder, str.join(".", [sim_info.reference, "html"]), rows_height
161
+ )
162
+
163
+ if reference_file_path is not None:
164
+ df_ref = pd.read_csv(reference_file_path, sep=";").set_index("time")
165
+ error_df = abs(df_ref - data)
166
+ write_figure(
167
+ error_df, sim_folder, LAUNCHER_COMPARE_TRACE_FILE_NAME, rows_height
168
+ )
169
+
170
+ return 0
171
+
172
+
173
+ if __name__ == "__main__":
174
+ # arguments handling
175
+
176
+ parser = argparse.ArgumentParser(
177
+ description=__doc__, formatter_class=argparse.HelpFormatter
178
+ )
179
+ parser.add_argument(
180
+ "simulation_configuration",
181
+ help="The simulation configuration file path.",
182
+ )
183
+ parser.add_argument(
184
+ "-d",
185
+ "--launcher_directory",
186
+ default="./",
187
+ required=False,
188
+ help="A valid folder for the launcher to run (initialized with the configure "
189
+ "script)",
190
+ )
191
+ parser.add_argument(
192
+ "-s",
193
+ "--series",
194
+ dest="series",
195
+ default="Z",
196
+ required=False,
197
+ help="The series name to save the simulation result.",
198
+ )
199
+ parser.add_argument(
200
+ "-m",
201
+ "--message",
202
+ dest="message",
203
+ default="",
204
+ required=False,
205
+ help="A optional comment on the simulation",
206
+ )
207
+ parser.add_argument(
208
+ "-v",
209
+ "--verbose",
210
+ dest="verbose",
211
+ action="store_true",
212
+ default=False,
213
+ required=False,
214
+ help="Display logs in console",
215
+ )
216
+
217
+ parser.add_argument(
218
+ "-l",
219
+ "--log_level",
220
+ dest="log_level",
221
+ default="INFO",
222
+ choices=["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"],
223
+ required=False,
224
+ help="Level of the console logs",
225
+ )
226
+
227
+ parser.add_argument(
228
+ "-ext",
229
+ "--file_extension",
230
+ dest="extension",
231
+ default="csv",
232
+ required=False,
233
+ help="The file extension to write the simulation results.",
234
+ choices=["csv", "parquet"],
235
+ )
236
+ parser.add_argument(
237
+ "-t",
238
+ "--trace",
239
+ dest="trace",
240
+ default="False",
241
+ required=False,
242
+ action="store_true",
243
+ help="Set to True to save a html graph of the results. Default is False",
244
+ )
245
+ parser.add_argument(
246
+ "--compare",
247
+ dest="reference",
248
+ required=False,
249
+ help="Set a reference file to compare against the simulation results"
250
+ "(column names must match).",
251
+ )
252
+ parser.add_argument(
253
+ "--rows_height",
254
+ dest="rows_height",
255
+ default=200.0,
256
+ required=False,
257
+ help="Height of each row in the graph if any.",
258
+ )
259
+ args = parser.parse_args()
260
+
261
+ # setup logger when verbose
262
+ if args.verbose is True:
263
+ stdout_handler = logging.StreamHandler(sys.stdout)
264
+ add_log_handler(stdout_handler, args.log_level)
265
+
266
+ # create paths from arguments
267
+ root_folder_path = Path(args.launcher_directory).absolute()
268
+ config_file_path = Path(args.simulation_configuration).absolute()
269
+ reference_file_path = (
270
+ Path(args.reference).absolute() if args.reference is not None else None
271
+ )
272
+
273
+ rows_heights = float(args.rows_height)
274
+ sys.exit(
275
+ main(
276
+ root_sim_directory=root_folder_path,
277
+ config_file_path=config_file_path,
278
+ series=args.series,
279
+ message=args.message,
280
+ extension=args.extension,
281
+ trace=args.trace,
282
+ reference_file_path=reference_file_path,
283
+ rows_height=rows_heights,
284
+ )
285
+ )