gammasimtools 0.22.0__py3-none-any.whl → 0.24.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 (128) hide show
  1. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/RECORD +128 -125
  3. simtools/_version.py +2 -2
  4. simtools/application_control.py +118 -0
  5. simtools/applications/calculate_incident_angles.py +17 -22
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +28 -43
  7. simtools/applications/convert_geo_coordinates_of_array_elements.py +26 -45
  8. simtools/applications/convert_model_parameter_from_simtel.py +21 -41
  9. simtools/applications/db_add_file_to_db.py +13 -14
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +20 -33
  11. simtools/applications/db_add_value_from_json_to_db.py +29 -24
  12. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +20 -35
  13. simtools/applications/db_generate_compound_indexes.py +11 -13
  14. simtools/applications/db_get_array_layouts_from_db.py +20 -40
  15. simtools/applications/db_get_file_from_db.py +15 -17
  16. simtools/applications/db_get_parameter_from_db.py +33 -35
  17. simtools/applications/db_inspect_databases.py +13 -12
  18. simtools/applications/db_upload_model_repository.py +13 -31
  19. simtools/applications/derive_ctao_array_layouts.py +16 -21
  20. simtools/applications/derive_mirror_rnda.py +9 -14
  21. simtools/applications/derive_photon_electron_spectrum.py +7 -10
  22. simtools/applications/derive_psf_parameters.py +13 -20
  23. simtools/applications/derive_trigger_rates.py +6 -9
  24. simtools/applications/docs_produce_array_element_report.py +22 -23
  25. simtools/applications/docs_produce_calibration_reports.py +26 -24
  26. simtools/applications/docs_produce_model_parameter_reports.py +15 -22
  27. simtools/applications/docs_produce_simulation_configuration_report.py +21 -22
  28. simtools/applications/generate_array_config.py +14 -33
  29. simtools/applications/generate_corsika_histograms.py +22 -43
  30. simtools/applications/generate_default_metadata.py +15 -36
  31. simtools/applications/generate_regular_arrays.py +11 -15
  32. simtools/applications/generate_simtel_event_data.py +23 -33
  33. simtools/applications/maintain_simulation_model_add_production.py +20 -37
  34. simtools/applications/maintain_simulation_model_compare_productions.py +10 -12
  35. simtools/applications/maintain_simulation_model_verify_production_tables.py +8 -11
  36. simtools/applications/merge_tables.py +14 -23
  37. simtools/applications/plot_array_layout.py +77 -54
  38. simtools/applications/plot_simtel_events.py +11 -13
  39. simtools/applications/plot_tabular_data.py +17 -38
  40. simtools/applications/plot_tabular_data_for_model_parameter.py +16 -23
  41. simtools/applications/print_version.py +14 -42
  42. simtools/applications/production_derive_corsika_limits.py +5 -9
  43. simtools/applications/production_derive_statistics.py +12 -25
  44. simtools/applications/production_generate_grid.py +20 -48
  45. simtools/applications/production_merge_corsika_limits.py +17 -21
  46. simtools/applications/run_application.py +12 -32
  47. simtools/applications/simulate_flasher.py +21 -25
  48. simtools/applications/simulate_illuminator.py +7 -14
  49. simtools/applications/simulate_pedestals.py +13 -13
  50. simtools/applications/simulate_prod.py +21 -33
  51. simtools/applications/simulate_prod_htcondor_generator.py +11 -25
  52. simtools/applications/submit_array_layouts.py +16 -19
  53. simtools/applications/submit_data_from_external.py +18 -34
  54. simtools/applications/submit_model_parameter_from_external.py +27 -40
  55. simtools/applications/validate_camera_efficiency.py +23 -21
  56. simtools/applications/validate_camera_fov.py +21 -26
  57. simtools/applications/validate_cumulative_psf.py +27 -35
  58. simtools/applications/validate_file_using_schema.py +15 -33
  59. simtools/applications/validate_optics.py +27 -33
  60. simtools/camera/camera_efficiency.py +0 -2
  61. simtools/configuration/commandline_parser.py +39 -13
  62. simtools/configuration/configurator.py +1 -6
  63. simtools/corsika/corsika_config.py +2 -9
  64. simtools/data_model/data_reader.py +0 -2
  65. simtools/data_model/metadata_collector.py +0 -2
  66. simtools/data_model/model_data_writer.py +1 -3
  67. simtools/data_model/schema.py +36 -34
  68. simtools/data_model/validate_data.py +0 -2
  69. simtools/db/db_handler.py +61 -296
  70. simtools/db/db_model_upload.py +1 -1
  71. simtools/db/mongo_db.py +535 -0
  72. simtools/dependencies.py +33 -8
  73. simtools/io/hdf5_handler.py +0 -5
  74. simtools/io/legacy_data_handler.py +0 -5
  75. simtools/job_execution/job_manager.py +0 -3
  76. simtools/layout/array_layout.py +7 -9
  77. simtools/layout/array_layout_utils.py +3 -3
  78. simtools/layout/telescope_position.py +0 -2
  79. simtools/model/array_model.py +38 -71
  80. simtools/model/calibration_model.py +12 -11
  81. simtools/model/camera.py +0 -2
  82. simtools/model/mirrors.py +0 -2
  83. simtools/model/model_parameter.py +200 -140
  84. simtools/model/model_repository.py +159 -35
  85. simtools/model/model_utils.py +3 -8
  86. simtools/model/site_model.py +59 -29
  87. simtools/model/telescope_model.py +21 -15
  88. simtools/production_configuration/calculate_statistical_uncertainties_grid_point.py +0 -2
  89. simtools/production_configuration/derive_production_statistics.py +0 -2
  90. simtools/production_configuration/interpolation_handler.py +0 -2
  91. simtools/ray_tracing/mirror_panel_psf.py +4 -4
  92. simtools/ray_tracing/psf_analysis.py +0 -2
  93. simtools/ray_tracing/psf_parameter_optimisation.py +1 -1
  94. simtools/ray_tracing/ray_tracing.py +0 -2
  95. simtools/reporting/docs_auto_report_generator.py +109 -1
  96. simtools/reporting/docs_read_parameters.py +4 -9
  97. simtools/runners/corsika_runner.py +0 -2
  98. simtools/runners/corsika_simtel_runner.py +0 -2
  99. simtools/runners/simtel_runner.py +0 -2
  100. simtools/schemas/model_parameters/transit_time_random.schema.yml +29 -0
  101. simtools/schemas/simulation_models_info.schema.yml +2 -1
  102. simtools/simtel/simtel_config_reader.py +0 -2
  103. simtools/simtel/simtel_config_writer.py +128 -33
  104. simtools/simtel/simtel_io_metadata.py +3 -3
  105. simtools/simtel/simulator_array.py +9 -21
  106. simtools/simtel/simulator_camera_efficiency.py +0 -2
  107. simtools/simtel/simulator_light_emission.py +1 -3
  108. simtools/simtel/simulator_ray_tracing.py +0 -2
  109. simtools/simulator.py +2 -6
  110. simtools/testing/assertions.py +52 -8
  111. simtools/testing/configuration.py +17 -4
  112. simtools/testing/validate_output.py +4 -8
  113. simtools/utils/general.py +5 -13
  114. simtools/utils/geometry.py +0 -5
  115. simtools/utils/names.py +1 -13
  116. simtools/utils/value_conversion.py +10 -5
  117. simtools/version.py +85 -0
  118. simtools/visualization/plot_array_layout.py +129 -23
  119. simtools/visualization/plot_incident_angles.py +0 -2
  120. simtools/visualization/plot_pixels.py +1 -1
  121. simtools/visualization/plot_psf.py +1 -1
  122. simtools/visualization/plot_simtel_events.py +0 -11
  123. simtools/visualization/plot_tables.py +1 -1
  124. simtools/visualization/visualize.py +0 -12
  125. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/WHEEL +0 -0
  126. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/entry_points.txt +0 -0
  127. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/licenses/LICENSE +0 -0
  128. {gammasimtools-0.22.0.dist-info → gammasimtools-0.24.0.dist-info}/top_level.txt +0 -0
simtools/simulator.py CHANGED
@@ -22,11 +22,6 @@ from simtools.simtel.simulator_array import SimulatorArray
22
22
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
23
23
  from simtools.version import semver_to_int
24
24
 
25
- __all__ = [
26
- "InvalidRunsToSimulateError",
27
- "Simulator",
28
- ]
29
-
30
25
 
31
26
  class InvalidRunsToSimulateError(Exception):
32
27
  """Exception for invalid runs to simulate."""
@@ -130,7 +125,7 @@ class Simulator:
130
125
  label=self.label,
131
126
  site=self.args_dict.get("site"),
132
127
  layout_name=self.args_dict.get("array_layout_name"),
133
- mongo_db_config=self.db_config,
128
+ db_config=self.db_config,
134
129
  model_version=version,
135
130
  sim_telarray_seeds={
136
131
  "seed": self._get_seed_for_random_instrument_instances(
@@ -145,6 +140,7 @@ class Simulator:
145
140
  calibration_device_types=self._get_calibration_device_types(
146
141
  self.args_dict.get("run_mode")
147
142
  ),
143
+ overwrite_model_parameters=self.args_dict.get("overwrite_model_parameters", None),
148
144
  )
149
145
  for version in versions
150
146
  ]
@@ -8,6 +8,8 @@ from pathlib import Path
8
8
  import numpy as np
9
9
  import yaml
10
10
 
11
+ from simtools.simtel.simtel_io_metadata import read_sim_telarray_metadata
12
+
11
13
  _logger = logging.getLogger(__name__)
12
14
 
13
15
 
@@ -62,9 +64,9 @@ def assert_n_showers_and_energy_range(file):
62
64
 
63
65
  simulated_energies = []
64
66
  simulation_config = {}
65
- with SimTelFile(file) as f:
67
+ with SimTelFile(file, skip_non_triggered=False) as f:
66
68
  simulation_config = f.mc_run_headers[0]
67
- for event in f.iter_mc_events():
69
+ for event in f:
68
70
  simulated_energies.append(event["mc_shower"]["energy"])
69
71
 
70
72
  # The relative tolerance is set to 1% because ~0.5% shower simulations do not
@@ -124,7 +126,35 @@ def assert_expected_output(file, expected_output):
124
126
  return True
125
127
 
126
128
 
127
- def check_output_from_sim_telarray(file, expected_output):
129
+ def assert_expected_simtel_metadata(file, expected_metadata):
130
+ """
131
+ Assert that expected metadata is present in the sim_telarray file.
132
+
133
+ Parameters
134
+ ----------
135
+ file: Path
136
+ Path to the sim_telarray file.
137
+ expected_metadata: dict
138
+ Expected metadata values.
139
+
140
+ """
141
+ global_meta, telescope_meta = read_sim_telarray_metadata(file)
142
+
143
+ for key, value in expected_metadata.items():
144
+ if key not in global_meta and key not in telescope_meta:
145
+ _logger.error(f"Metadata key {key} not found in sim_telarray file {file}")
146
+ return False
147
+ if key in global_meta and global_meta[key] != value:
148
+ _logger.error(
149
+ f"Metadata key {key} has value {global_meta[key]} instead of expected {value}"
150
+ )
151
+ return False
152
+ _logger.debug(f"Metadata key {key} matches expected value {value}")
153
+
154
+ return True
155
+
156
+
157
+ def check_output_from_sim_telarray(file, file_test):
128
158
  """
129
159
  Check that the sim_telarray simulation result is reasonable and matches the expected output.
130
160
 
@@ -132,20 +162,34 @@ def check_output_from_sim_telarray(file, expected_output):
132
162
  ----------
133
163
  file: Path
134
164
  Path to the sim_telarray file.
135
- expected_output: dict
136
- Expected output values.
165
+ file_test: dict
166
+ File test description including expected output and metadata.
137
167
 
138
168
  Raises
139
169
  ------
140
170
  ValueError
141
171
  If the file is not a zstd compressed file.
142
172
  """
173
+ if "expected_output" not in file_test and "expected_simtel_metadata" not in file_test:
174
+ _logger.debug(f"No expected output or metadata provided, skipping checks {file_test}")
175
+ return True
176
+
143
177
  if file.suffix != ".zst":
144
178
  raise ValueError(
145
179
  f"Expected output file {file} is not a zstd compressed file "
146
180
  f"(i.e., a sim_telarray file)."
147
181
  )
148
182
 
149
- return assert_n_showers_and_energy_range(file=file) and assert_expected_output(
150
- file=file, expected_output=expected_output
151
- )
183
+ assert_output = assert_metadata = True
184
+
185
+ if "expected_output" in file_test:
186
+ assert_output = assert_expected_output(
187
+ file=file, expected_output=file_test["expected_output"]
188
+ )
189
+
190
+ if "expected_simtel_metadata" in file_test:
191
+ assert_metadata = assert_expected_simtel_metadata(
192
+ file=file, expected_metadata=file_test["expected_simtel_metadata"]
193
+ )
194
+
195
+ return assert_n_showers_and_energy_range(file=file) and assert_output and assert_metadata
@@ -5,6 +5,7 @@ import os
5
5
  from pathlib import Path
6
6
 
7
7
  import simtools.utils.general as gen
8
+ import simtools.version as simtools_version
8
9
  from simtools.io import ascii_handler
9
10
 
10
11
  _logger = logging.getLogger(__name__)
@@ -130,11 +131,23 @@ def configure(config, tmp_test_directory, request):
130
131
 
131
132
 
132
133
  def _skip_test_for_model_version(config, model_version_requested):
133
- """Skip test if model version requested is not supported."""
134
- if config.get("model_version_use_current") is None or model_version_requested is None:
134
+ """
135
+ Skip test if model version requested is not supported.
136
+
137
+ Compares full and major.minor version strings.
138
+ """
139
+ if not (config.get("model_version_use_current") and model_version_requested):
135
140
  return
136
- model_version_config = config["configuration"]["model_version"]
137
- if model_version_requested != model_version_config:
141
+ model_version_config = str(config["configuration"]["model_version"])
142
+
143
+ if (
144
+ simtools_version.compare_versions(
145
+ model_version_requested,
146
+ model_version_config,
147
+ level=simtools_version.version_kind(model_version_requested),
148
+ )
149
+ != 0
150
+ ):
138
151
  raise VersionError(
139
152
  f"Model version requested {model_version_requested} not supported for this test"
140
153
  )
@@ -117,11 +117,7 @@ def _validate_output_path_and_file(config, integration_file_tests):
117
117
  except AssertionError as exc:
118
118
  raise AssertionError(f"Output file {output_file_path} does not exist. ") from exc
119
119
 
120
- if "expected_output" in file_test:
121
- assert assertions.check_output_from_sim_telarray(
122
- output_file_path,
123
- file_test["expected_output"],
124
- )
120
+ assert assertions.check_output_from_sim_telarray(output_file_path, file_test)
125
121
 
126
122
 
127
123
  def _validate_model_parameter_json_file(config, model_parameter_validation, db_config):
@@ -139,7 +135,7 @@ def _validate_model_parameter_json_file(config, model_parameter_validation, db_c
139
135
 
140
136
  """
141
137
  _logger.info(f"Checking model parameter json file: {model_parameter_validation}")
142
- db = db_handler.DatabaseHandler(mongo_db_config=db_config)
138
+ db = db_handler.DatabaseHandler(db_config=db_config)
143
139
 
144
140
  reference_parameter_name = model_parameter_validation.get("reference_parameter_name")
145
141
 
@@ -346,7 +342,7 @@ def _validate_simtel_cfg_files(config, simtel_cfg_file):
346
342
  f"Comparing simtel cfg files: {reference_file} and {test_file} "
347
343
  f"for model version {config['configuration']['model_version']}"
348
344
  )
349
- return _compare_simtel_cfg_files(reference_file, test_file)
345
+ assert _compare_simtel_cfg_files(reference_file, test_file)
350
346
 
351
347
 
352
348
  def _compare_simtel_cfg_files(reference_file, test_file):
@@ -382,7 +378,7 @@ def _compare_simtel_cfg_files(reference_file, test_file):
382
378
  return False
383
379
 
384
380
  for ref_line, test_line in zip(reference_cfg, test_cfg):
385
- if any(ignore in ref_line for ignore in ("config_release", "Label")):
381
+ if any(ignore in ref_line for ignore in ("config_release", "Label", "simtools_version")):
386
382
  continue
387
383
  if ref_line != test_line:
388
384
  _logger.error(
simtools/utils/general.py CHANGED
@@ -13,17 +13,6 @@ from urllib.parse import urlparse
13
13
 
14
14
  import numpy as np
15
15
 
16
- __all__ = [
17
- "change_dict_keys_case",
18
- "clear_default_sim_telarray_cfg_directories",
19
- "collect_final_lines",
20
- "collect_kwargs",
21
- "get_log_excerpt",
22
- "get_log_level_from_user",
23
- "remove_substring_recursively_from_dict",
24
- "set_default_kwargs",
25
- ]
26
-
27
16
  _logger = logging.getLogger(__name__)
28
17
 
29
18
 
@@ -345,7 +334,7 @@ def resolve_file_patterns(file_names):
345
334
  return _files
346
335
 
347
336
 
348
- def pack_tar_file(tar_file_name, file_list):
337
+ def pack_tar_file(tar_file_name, file_list, sub_dir=None):
349
338
  """
350
339
  Pack files into a tar.gz archive.
351
340
 
@@ -355,6 +344,8 @@ def pack_tar_file(tar_file_name, file_list):
355
344
  Name of the output tar.gz file.
356
345
  file_list: list
357
346
  List of files to include in the archive.
347
+ sub_dir: str, optional
348
+ Subdirectory within the archive to place the files.
358
349
  """
359
350
  file_list = [Path(f) for f in file_list]
360
351
  base = Path(os.path.commonpath([f.resolve() for f in file_list]))
@@ -365,7 +356,8 @@ def pack_tar_file(tar_file_name, file_list):
365
356
 
366
357
  with tarfile.open(tar_file_name, "w:gz") as tar:
367
358
  for file in file_list:
368
- tar.add(file, arcname=file.name)
359
+ arc_name = Path(sub_dir) / file.name if sub_dir else file.name
360
+ tar.add(file, arcname=str(arc_name))
369
361
 
370
362
 
371
363
  def get_log_excerpt(log_file, n_last_lines=30):
@@ -7,11 +7,6 @@ import astropy.units as u
7
7
  import numpy as np
8
8
  from astropy.units import UnitsError
9
9
 
10
- __all__ = [
11
- "convert_2d_to_radial_distr",
12
- "rotate",
13
- ]
14
-
15
10
  _logger = logging.getLogger(__name__)
16
11
 
17
12
 
simtools/utils/names.py CHANGED
@@ -28,18 +28,6 @@ from simtools.constants import (
28
28
 
29
29
  _logger = logging.getLogger(__name__)
30
30
 
31
- __all__ = [
32
- "generate_file_name",
33
- "get_array_element_type_from_name",
34
- "get_site_from_array_element_name",
35
- "sanitize_name",
36
- "simtel_config_file_name",
37
- "simtel_single_mirror_list_file_name",
38
- "validate_array_element_id_name",
39
- "validate_array_element_name",
40
- "validate_site_name",
41
- ]
42
-
43
31
  # Mapping of db collection names to class keys
44
32
  db_collections_to_class_keys = {
45
33
  "sites": ["Site"],
@@ -122,7 +110,7 @@ def site_names():
122
110
  @cache
123
111
  def array_element_design_types(array_element_type):
124
112
  """
125
- Get array element site types (e.g., 'design' or 'flashcam').
113
+ Get array element design type (e.g., 'design' or 'flashcam').
126
114
 
127
115
  Default values are ['design', 'test'].
128
116
 
@@ -170,16 +170,21 @@ def get_value_as_quantity(value, unit):
170
170
 
171
171
  Raises
172
172
  ------
173
- u.UnitConversionError
173
+ ValueError
174
174
  If the value cannot be converted to the given unit.
175
175
  """
176
176
  if isinstance(value, u.Quantity):
177
177
  try:
178
178
  return value.to(unit)
179
- except u.UnitConversionError:
180
- _logger.error(f"Cannot convert {value.unit} to {unit}.")
181
- raise
182
- return value * unit
179
+ except u.UnitConversionError as exc:
180
+ raise ValueError(f"Cannot convert {value} with unit {value.unit} to {unit}.") from exc
181
+ elif not isinstance(value, int | float):
182
+ return value
183
+
184
+ if unit is None or unit == "null":
185
+ return value * u.dimensionless_unscaled
186
+
187
+ return value * u.Unit(unit)
183
188
 
184
189
 
185
190
  def _unit_as_string(unit):
simtools/version.py CHANGED
@@ -4,8 +4,12 @@
4
4
  # which is adapted from https://github.com/astropy/astropy/blob/master/astropy/version.py
5
5
  # see https://github.com/astropy/astropy/pull/10774 for a discussion on why this needed.
6
6
 
7
+ from packaging.specifiers import SpecifierSet
7
8
  from packaging.version import InvalidVersion, Version
8
9
 
10
+ MAJOR_MINOR_PATCH = "major.minor.patch"
11
+ MAJOR_MINOR = "major.minor"
12
+
9
13
  try:
10
14
  try:
11
15
  from ._dev_version import version
@@ -128,3 +132,84 @@ def sort_versions(version_list, reverse=False):
128
132
  return [str(v) for v in sorted(map(Version, version_list), reverse=reverse)]
129
133
  except InvalidVersion as exc:
130
134
  raise ValueError(f"Invalid version in list: {version_list}") from exc
135
+
136
+
137
+ def version_kind(version_string):
138
+ """
139
+ Determine the kind of version string.
140
+
141
+ Parameters
142
+ ----------
143
+ version_string : str
144
+ The version string to analyze.
145
+
146
+ Returns
147
+ -------
148
+ str
149
+ The kind of version string ("major.minor", "major.minor.patch", or "major").
150
+ """
151
+ try:
152
+ ver = Version(version_string)
153
+ except InvalidVersion as exc:
154
+ raise ValueError(f"Invalid version string: {version_string}") from exc
155
+ if ver.release and len(ver.release) >= 3:
156
+ return MAJOR_MINOR_PATCH
157
+ if len(ver.release) == 2:
158
+ return MAJOR_MINOR
159
+ return "major"
160
+
161
+
162
+ def compare_versions(version_string_1, version_string_2, level=MAJOR_MINOR_PATCH):
163
+ """
164
+ Compare two versions at the given level: "major", "major.minor", "major.minor.patch".
165
+
166
+ Parameters
167
+ ----------
168
+ version_string_1 : str
169
+ First version string to compare.
170
+ version_string_2 : str
171
+ Second version string to compare.
172
+ level : str, optional
173
+ Level of comparison: "major", "major.minor", or "major.minor.patch"
174
+
175
+ Returns
176
+ -------
177
+ int
178
+ -1 if version_string_1 < version_string_2
179
+ 0 if version_string_1 == version_string_2
180
+ 1 if version_string_1 > version_string_2
181
+ """
182
+ ver1 = Version(version_string_1).release
183
+ ver2 = Version(version_string_2).release
184
+
185
+ if level == "major":
186
+ ver1, ver2 = ver1[:1], ver2[:1]
187
+ elif level == MAJOR_MINOR:
188
+ ver1, ver2 = ver1[:2], ver2[:2]
189
+ elif level != MAJOR_MINOR_PATCH:
190
+ raise ValueError(f"Unknown level: {level}")
191
+
192
+ return (ver1 > ver2) - (ver1 < ver2)
193
+
194
+
195
+ def check_version_constraint(version_string, constraint):
196
+ """
197
+ Check if a version satisfies a constraint.
198
+
199
+ Parameters
200
+ ----------
201
+ version_string : str
202
+ The version string to check (e.g., "6.0.2").
203
+ constraint : str
204
+ The version constraint to check against (e.g., ">=6.0.0").
205
+
206
+ Returns
207
+ -------
208
+ bool
209
+ True if the version satisfies the constraint, False otherwise.
210
+ """
211
+ spec = SpecifierSet(constraint.strip(), prereleases=True)
212
+ ver = Version(version_string)
213
+ if ver in spec:
214
+ return True
215
+ return False