bmh 2026.1__tar.gz

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 (39) hide show
  1. bmh-2026.1/PKG-INFO +37 -0
  2. bmh-2026.1/README.md +6 -0
  3. bmh-2026.1/pyproject.toml +38 -0
  4. bmh-2026.1/src/bmh/__init__.py +0 -0
  5. bmh-2026.1/src/bmh/benchmark/__init__.py +1 -0
  6. bmh-2026.1/src/bmh/benchmark/core.py +159 -0
  7. bmh-2026.1/src/bmh/benchmark/data.py +211 -0
  8. bmh-2026.1/src/bmh/benchmark/material_deposition.py +398 -0
  9. bmh-2026.1/src/bmh/benchmark/reference_meta.py +57 -0
  10. bmh-2026.1/src/bmh/benchmark/simulator_meta.py +63 -0
  11. bmh-2026.1/src/bmh/helpers/__init__.py +0 -0
  12. bmh-2026.1/src/bmh/helpers/identifiers.py +19 -0
  13. bmh-2026.1/src/bmh/helpers/math.py +14 -0
  14. bmh-2026.1/src/bmh/helpers/reclaimed_material_evaluator.py +56 -0
  15. bmh-2026.1/src/bmh/helpers/simple.py +62 -0
  16. bmh-2026.1/src/bmh/helpers/stockpile_math.py +82 -0
  17. bmh-2026.1/src/bmh/helpers/test/__init__.py +0 -0
  18. bmh-2026.1/src/bmh/helpers/test/test_identifiers.py +24 -0
  19. bmh-2026.1/src/bmh/helpers/test/test_stockpile_math.py +63 -0
  20. bmh-2026.1/src/bmh/optimization/__init__.py +1 -0
  21. bmh-2026.1/src/bmh/optimization/homogenization_problem/__init__.py +0 -0
  22. bmh-2026.1/src/bmh/optimization/homogenization_problem/generator.py +84 -0
  23. bmh-2026.1/src/bmh/optimization/homogenization_problem/homogenization_problem.py +353 -0
  24. bmh-2026.1/src/bmh/optimization/homogenization_problem/test/__init__.py +0 -0
  25. bmh-2026.1/src/bmh/optimization/homogenization_problem/test/test_homogenization_problem.py +146 -0
  26. bmh-2026.1/src/bmh/optimization/optimization.py +478 -0
  27. bmh-2026.1/src/bmh/optimization/optimization_result.py +12 -0
  28. bmh-2026.1/src/bmh/optimization/plot_server/__init__.py +0 -0
  29. bmh-2026.1/src/bmh/optimization/plot_server/bokeh_plot_server.py +388 -0
  30. bmh-2026.1/src/bmh/optimization/plot_server/plot_server.py +88 -0
  31. bmh-2026.1/src/bmh/simulation/__init__.py +1 -0
  32. bmh-2026.1/src/bmh/simulation/blending_simulator.py +77 -0
  33. bmh-2026.1/src/bmh/simulation/bsl_blending_simulator.py +88 -0
  34. bmh-2026.1/src/bmh/simulation/mathematical_blending/__init__.py +0 -0
  35. bmh-2026.1/src/bmh/simulation/mathematical_blending/mathematical_blending.py +21 -0
  36. bmh-2026.1/src/bmh/simulation/mathematical_blending/tests/__init__.py +0 -0
  37. bmh-2026.1/src/bmh/simulation/mathematical_blending/tests/test_mathematical_blending.py +30 -0
  38. bmh-2026.1/src/bmh/simulation/mathematical_blending_simulator.py +26 -0
  39. bmh-2026.1/src/bmh/simulation/smooth_blending_simulator.py +33 -0
bmh-2026.1/PKG-INFO ADDED
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: bmh
3
+ Version: 2026.1
4
+ Summary: Library for Bulk Material Homogenization simulation and optimization
5
+ Keywords: bulk-material,homogenization,simulation,optimization,bmh,jmetalpy
6
+ Author: Michael Cipold
7
+ Author-email: Michael Cipold <github@cipold.de>
8
+ License-Expression: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Requires-Dist: jmetalpy==1.5.5
14
+ Requires-Dist: numpy>=1.20.0
15
+ Requires-Dist: pandas>=1.3.0
16
+ Requires-Dist: bmh-jmetalpy-extensions~=2026.1
17
+ Requires-Dist: blending-simulator-lib~=2026.1
18
+ Requires-Dist: bokeh ; extra == 'bokehplotserver'
19
+ Requires-Dist: dask ; extra == 'daskevaluator'
20
+ Requires-Dist: pytest ; extra == 'dev'
21
+ Requires-Dist: dask ; extra == 'distributedevaluator'
22
+ Requires-Dist: distributed ; extra == 'distributedevaluator'
23
+ Requires-Python: >=3.10
24
+ Project-URL: Homepage, https://github.com/jcbachmann/blending-evaluation
25
+ Project-URL: Repository, https://github.com/jcbachmann/blending-evaluation
26
+ Provides-Extra: bokehplotserver
27
+ Provides-Extra: daskevaluator
28
+ Provides-Extra: dev
29
+ Provides-Extra: distributedevaluator
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Bulk Material Homogenization (bmh)
33
+
34
+ [![PyPI version](https://img.shields.io/pypi/v/bmh.svg)](https://pypi.org/project/bmh/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
+
37
+ Library for simulation, optimization, and evaluation of Bulk Material Homogenization processes.
bmh-2026.1/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # Bulk Material Homogenization (bmh)
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/bmh.svg)](https://pypi.org/project/bmh/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Library for simulation, optimization, and evaluation of Bulk Material Homogenization processes.
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "bmh"
3
+ version = "2026.1"
4
+ description = "Library for Bulk Material Homogenization simulation and optimization"
5
+ keywords = ["bulk-material", "homogenization", "simulation", "optimization", "bmh", "jmetalpy"]
6
+ readme = "README.md"
7
+ authors = [
8
+ { name = "Michael Cipold", email = "github@cipold.de" }
9
+ ]
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "jmetalpy==1.5.5",
13
+ "numpy>=1.20.0",
14
+ "pandas>=1.3.0",
15
+ "bmh_jmetalpy_extensions~=2026.1",
16
+ "blending_simulator_lib~=2026.1",
17
+ ]
18
+ license = "MIT"
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "Operating System :: OS Independent",
22
+ "Development Status :: 4 - Beta",
23
+ "Topic :: Scientific/Engineering",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/jcbachmann/blending-evaluation"
28
+ Repository = "https://github.com/jcbachmann/blending-evaluation"
29
+
30
+ [project.optional-dependencies]
31
+ BokehPlotServer = ["bokeh"]
32
+ DaskEvaluator = ["dask"]
33
+ DistributedEvaluator = ["dask", "distributed"]
34
+ dev = ["pytest"]
35
+
36
+ [build-system]
37
+ requires = ["uv_build>=0.11.3,<0.12"]
38
+ build-backend = "uv_build"
File without changes
@@ -0,0 +1 @@
1
+ name = "benchmark"
@@ -0,0 +1,159 @@
1
+ import json
2
+ import logging
3
+ import os
4
+
5
+ from ..helpers import math
6
+ from ..helpers.reclaimed_material_evaluator import ReclaimedMaterialEvaluator
7
+ from .data import BenchmarkData, prepare_path
8
+ from .material_deposition import DepositionMeta, MaterialDeposition, MaterialMeta, write_data_file
9
+ from .reference_meta import ReferenceMeta
10
+ from .simulator_meta import SimulatorMeta
11
+
12
+
13
+ def test_simulator(simulator_meta: SimulatorMeta):
14
+ logger = logging.getLogger(__name__)
15
+ logger.debug(f'Acquiring simulator type for "{simulator_meta.type}"')
16
+
17
+ # Acquire simulator type
18
+ sim_type = simulator_meta.get_type()
19
+
20
+ # Read simulator parameters
21
+ sim_params = simulator_meta.get_params()
22
+
23
+ sim_params_copy = sim_params.copy()
24
+ sim_params_copy["bed_size_x"] = 10.0
25
+ sim_params_copy["bed_size_z"] = 10.0
26
+
27
+ # Create demo simulator to test if params are all accepted
28
+ # If no exception occurs everything seems to be fine
29
+ logger.debug("Testing creation of simulator")
30
+ _sim = sim_type(**sim_params_copy)
31
+
32
+ logger.debug("Testing deletion of simulator")
33
+ del _sim
34
+
35
+
36
+ def compute_sigma(material_meta: MaterialMeta):
37
+ logger = logging.getLogger(__name__)
38
+ logger.debug(f'Computing standard deviation sigma for material parameters of "{material_meta}"')
39
+
40
+ material = material_meta.get_material()
41
+ parameter_columns = material.get_parameter_columns()
42
+ sigmas = {}
43
+
44
+ for parameter_column in parameter_columns:
45
+ _mean, sigma = math.weighted_avg_and_std(
46
+ values=material.data[parameter_column].values,
47
+ weights=material.data["volume"].values,
48
+ )
49
+
50
+ logger.debug(f"{material_meta} - {parameter_column}: sigma = {sigma}")
51
+ sigmas[parameter_column] = sigma
52
+
53
+ return sigmas
54
+
55
+
56
+ def compute_sigma_reduction(material_before: MaterialMeta, material_after: MaterialMeta):
57
+ logger = logging.getLogger(__name__)
58
+ sigmas_before = compute_sigma(material_before)
59
+ sigmas_after = compute_sigma(material_after)
60
+
61
+ logger.info(f'Sigma reduction ratios for "{material_before}" -> "{material_after}":')
62
+ for parameter, sigma_before in sigmas_before.items():
63
+ reduction = sigma_before / sigmas_after[parameter]
64
+ logger.info(f"{parameter}\t1:{reduction:.2f}")
65
+
66
+
67
+ def process(identifier: str, material_meta: MaterialMeta, deposition_meta: DepositionMeta, simulator_meta: SimulatorMeta, path: str, dry_run: bool) -> None:
68
+ logger = logging.getLogger(__name__)
69
+ logger.info(f'Processing "{identifier}" with material "{material_meta}" and deposition "{deposition_meta}" using simulator type {simulator_meta.type}')
70
+
71
+ logger.debug("Creating simulator")
72
+ sim_params = simulator_meta.get_params().copy()
73
+ sim_params["bed_size_x"] = deposition_meta.bed_size_x
74
+ sim_params["bed_size_z"] = deposition_meta.bed_size_z
75
+ sim = simulator_meta.get_type()(**sim_params)
76
+
77
+ logger.debug("Combining material and deposition")
78
+ material_deposition = MaterialDeposition(material_meta.get_material(), deposition_meta.get_deposition())
79
+ logger.debug(f"Material and deposition combined:\n{material_deposition.data.describe()}")
80
+
81
+ logger.debug("Stacking and reclaiming material")
82
+ reclaimed_material = sim.stack_reclaim(material_deposition)
83
+ logger.debug(f"Reclaimed material:\n{reclaimed_material.data.describe()}")
84
+
85
+ directory = os.path.join(path, BenchmarkData.REFERENCE_DIR, identifier)
86
+ prepare_path(directory, dry_run=dry_run)
87
+
88
+ simulator_file = os.path.join(directory, BenchmarkData.SIMULATOR_JSON)
89
+ logger.debug(f'Writing simulator type and parameters to "{simulator_file}"')
90
+ if not dry_run:
91
+ with open(simulator_file, "w") as file:
92
+ json.dump({"simulator": str(simulator_meta)}, file, indent=4)
93
+
94
+ reclaimed_reference = ReferenceMeta(
95
+ identifier,
96
+ directory,
97
+ {
98
+ "material": str(material_meta),
99
+ "deposition": str(deposition_meta),
100
+ },
101
+ )
102
+
103
+ meta_file = os.path.join(directory, BenchmarkData.META_JSON)
104
+ logger.debug(f'Writing reference meta to "{meta_file}"')
105
+ if not dry_run:
106
+ with open(meta_file, "w") as file:
107
+ json.dump(
108
+ reclaimed_reference.to_dict(),
109
+ file,
110
+ indent=4,
111
+ )
112
+
113
+ reclaimed_material_path = os.path.join(directory, BenchmarkData.MATERIAL_DIR)
114
+ reclaimed_material_meta = MaterialMeta(
115
+ identifier,
116
+ path="",
117
+ meta_dict={
118
+ "label": "Reclaimed " + identifier,
119
+ "description": "Reclaimed material from " + identifier,
120
+ "category": "reclaimed",
121
+ "time": reclaimed_material.data.timestamp.max(),
122
+ "volume": reclaimed_material.data.volume.sum(),
123
+ "data": BenchmarkData.DATA_CSV,
124
+ },
125
+ )
126
+ reclaimed_material.meta = reclaimed_material_meta
127
+ reclaimed_material_meta.data = reclaimed_material
128
+
129
+ logger.debug(f'Creating directory "{reclaimed_material_path}"')
130
+ prepare_path(reclaimed_material_path, dry_run=dry_run)
131
+
132
+ reclaimed_material_meta_file = os.path.join(reclaimed_material_path, BenchmarkData.META_JSON)
133
+ logger.debug(f'Writing reclaimed material meta to "{reclaimed_material_meta_file}"')
134
+ if not dry_run:
135
+ with open(reclaimed_material_meta_file, "w") as file:
136
+ json.dump(
137
+ reclaimed_material_meta.to_dict(),
138
+ file,
139
+ indent=4,
140
+ )
141
+
142
+ if not dry_run:
143
+ write_data_file(
144
+ data=reclaimed_material.data,
145
+ data_file=os.path.join(reclaimed_material_path, BenchmarkData.DATA_CSV),
146
+ )
147
+
148
+ z_center = 0.5 * deposition_meta.bed_size_z
149
+ evaluator = ReclaimedMaterialEvaluator(
150
+ reclaimed_material,
151
+ x_min=z_center,
152
+ x_max=deposition_meta.bed_size_x - z_center,
153
+ )
154
+ parameter_stdev = evaluator.get_parameter_stdev()
155
+ logger.info("Parameter standard deviations:")
156
+ for k, v in parameter_stdev.items():
157
+ logger.info(f"{k}\t{v}")
158
+
159
+ # compute_sigma_reduction(material_meta, reclaimed_material_meta)
@@ -0,0 +1,211 @@
1
+ import json
2
+ import logging
3
+ import os
4
+
5
+ from .material_deposition import DepositionMeta, MaterialMeta, write_data_file
6
+ from .reference_meta import ReferenceMeta
7
+ from .simulator_meta import SIMULATOR_TYPE, SimulatorMeta
8
+
9
+
10
+ def create_instance_for_each_managed_dir(path: str, instance_type: type, *, recursive: bool = False) -> dict:
11
+ logger = logging.getLogger(__name__)
12
+ logger.debug(f'Creating class "{instance_type.__name__}" for each managed directory in path "{path}"')
13
+ entries = {}
14
+ for entry in os.listdir(path):
15
+ entry_path = os.path.join(path, entry)
16
+
17
+ if os.path.isdir(entry_path):
18
+ meta_path = os.path.join(entry_path, BenchmarkData.META_JSON)
19
+ if os.path.isfile(meta_path):
20
+ logger.debug(f'Reading "{instance_type.__name__}" from path "{entry_path}"')
21
+
22
+ # Errors which occur during JSON parsing and interpretation should interrupt program execution
23
+ with open(meta_path) as file:
24
+ meta = json.load(file)
25
+ entries[entry] = instance_type(entry, os.path.abspath(entry_path), meta)
26
+ else:
27
+ if recursive:
28
+ sub_entries = create_instance_for_each_managed_dir(entry_path, instance_type, recursive=True)
29
+ duplicate_identifiers = set(entries.keys()).intersection(sub_entries.keys())
30
+ if len(duplicate_identifiers) > 0:
31
+ raise ValueError(f"Duplicate identifiers {duplicate_identifiers}")
32
+ entries.update(sub_entries)
33
+ else:
34
+ logger.debug(f'Ignoring directory "{entry_path}"')
35
+ else:
36
+ logger.debug(f'Ignoring file "{entry_path}"')
37
+
38
+ return entries
39
+
40
+
41
+ def prepare_path(path: str, *, dry_run: bool = False, empty_required: bool = True):
42
+ logger = logging.getLogger(__name__)
43
+ logger.debug(f'Preparing path "{path}"')
44
+ if os.path.exists(path):
45
+ if os.path.isdir(path):
46
+ if empty_required:
47
+ if len(os.listdir(path)) > 0:
48
+ raise OSError(f'Path "{path}" is not empty')
49
+ logger.debug(f'Path "{path}" already exists and is empty')
50
+ else:
51
+ logger.debug(f'Path "{path}" already exists')
52
+ else:
53
+ raise OSError(f'Path "{path}" exists but is not a directory')
54
+ else:
55
+ logger.debug(f'Creating path "{path}"')
56
+ if not dry_run:
57
+ os.makedirs(path)
58
+
59
+
60
+ class BenchmarkData:
61
+ META_JSON = "meta.json"
62
+ MATERIAL_DIR = "material"
63
+ DEPOSITION_DIR = "deposition"
64
+ SIMULATOR_DIR = "simulator"
65
+ REFERENCE_DIR = "reference"
66
+ BENCHMARK_DIR = "benchmark"
67
+ DATA_CSV = "data.csv"
68
+ PREDICTION_CSV = "prediction.csv"
69
+ SIMULATOR_JSON = "simulator.json"
70
+
71
+ def __init__(self, base_path: str | None = None):
72
+ self.base_path = base_path
73
+
74
+ self.materials: dict[str, MaterialMeta] = {}
75
+ self.depositions: dict[str, DepositionMeta] = {}
76
+ self.simulators: dict[str, SimulatorMeta] = {}
77
+
78
+ self.logger = logging.getLogger(__name__)
79
+
80
+ def get_benchmark_dir_path(self, path: str | None, benchmark_dir: str):
81
+ if path:
82
+ return path
83
+ if self.base_path:
84
+ return os.path.join(self.base_path, benchmark_dir)
85
+ raise ValueError("Base path not provided")
86
+
87
+ def read_materials(self, path: str | None = None) -> dict[str, MaterialMeta]:
88
+ path = self.get_benchmark_dir_path(path, BenchmarkData.MATERIAL_DIR)
89
+ self.logger.debug("Reading materials")
90
+ self.materials = create_instance_for_each_managed_dir(path, MaterialMeta, recursive=True)
91
+ self.logger.info(f"{len(self.materials)} materials read")
92
+ return self.materials
93
+
94
+ def read_depositions(self, path: str | None = None) -> dict[str, DepositionMeta]:
95
+ path = self.get_benchmark_dir_path(path, BenchmarkData.DEPOSITION_DIR)
96
+ self.logger.debug("Reading depositions")
97
+ self.depositions = create_instance_for_each_managed_dir(path, DepositionMeta, recursive=True)
98
+ self.logger.info(f"{len(self.depositions)} depositions read")
99
+ return self.depositions
100
+
101
+ def read_simulators(self, path: str | None = None) -> dict[str, SimulatorMeta]:
102
+ path = self.get_benchmark_dir_path(path, BenchmarkData.SIMULATOR_DIR)
103
+ self.logger.debug("Reading simulators")
104
+ self.simulators = create_instance_for_each_managed_dir(path, SimulatorMeta, recursive=True)
105
+ self.logger.info(f"{len(self.simulators)} simulators read")
106
+ return self.simulators
107
+
108
+ def read_references(self, path: str | None = None) -> dict[str, ReferenceMeta]:
109
+ path = self.get_benchmark_dir_path(path, BenchmarkData.REFERENCE_DIR)
110
+ self.logger.debug("Reading references")
111
+ references = create_instance_for_each_managed_dir(path, ReferenceMeta, recursive=True)
112
+ self.logger.info(f"{len(references)} references read")
113
+ return references
114
+
115
+ def read_base(self, path: str | None = None) -> None:
116
+ if not path and not self.base_path:
117
+ raise ValueError("Neither path nor base path provided")
118
+
119
+ self.read_materials(path)
120
+ self.read_depositions(path)
121
+ self.read_simulators(path)
122
+
123
+ def validate_references(self, references: dict[str, ReferenceMeta]):
124
+ self.logger.debug("Validating references")
125
+ for reference in references.values():
126
+ self.logger.debug(f'Validating reference "{reference}"')
127
+ if reference.material not in self.materials:
128
+ raise ValueError(f'Material "{reference.material}" not found in materials')
129
+ if reference.deposition not in self.depositions:
130
+ raise ValueError(f'Deposition "{reference.deposition}" not found in depositions')
131
+ self.logger.info("References validated")
132
+
133
+ def validate_simulators(self, sim_identifiers: list[str]):
134
+ self.logger.debug("Validating simulators")
135
+ for sim_identifier in sim_identifiers:
136
+ self.logger.debug(f'Validating simulator identifier "{sim_identifier}"')
137
+ if sim_identifier not in self.simulators:
138
+ raise ValueError(f'Simulator identifier "{sim_identifier}" not found in simulators')
139
+ if self.simulators[sim_identifier].type not in SIMULATOR_TYPE:
140
+ raise ValueError(f'Simulator type "{self.simulators[sim_identifier]}" not found for identifier "{sim_identifier}"')
141
+ self.logger.info("Simulators validated")
142
+
143
+ def validate_simulator(self, sim_identifier: str):
144
+ self.logger.debug(f'Validating simulator identifier "{sim_identifier}"')
145
+ if sim_identifier not in self.simulators:
146
+ raise ValueError(f'Simulator identifier "{sim_identifier}" not found in simulators')
147
+ if self.simulators[sim_identifier].type not in SIMULATOR_TYPE:
148
+ raise ValueError(f'Simulator type "{self.simulators[sim_identifier]}" not found for identifier "{sim_identifier}"')
149
+ self.logger.info(f'Simulator identifier "{sim_identifier}" validated')
150
+
151
+ def get_simulator_meta(self, sim_identifier: str) -> SimulatorMeta:
152
+ self.validate_simulator(sim_identifier)
153
+ return self.simulators[sim_identifier]
154
+
155
+ def validate_material(self, material_identifier: str):
156
+ self.logger.debug(f'Validating material identifier "{material_identifier}"')
157
+ if material_identifier not in self.materials:
158
+ raise ValueError(f'Material "{material_identifier}" not found in materials')
159
+ self.logger.info(f'Material identifier "{material_identifier}" validated')
160
+
161
+ def validate_deposition(self, deposition_identifiers: str):
162
+ self.logger.debug(f'Validating deposition identifier "{deposition_identifiers}"')
163
+ if deposition_identifiers not in self.depositions:
164
+ raise ValueError(f'Deposition "{deposition_identifiers}" not found in depositions')
165
+ self.logger.info(f'Deposition identifier "{deposition_identifiers}" validated')
166
+
167
+ def get_material_meta(self, material_identifier: str) -> MaterialMeta:
168
+ self.validate_material(material_identifier)
169
+ return self.materials[material_identifier]
170
+
171
+ def get_deposition_meta(self, deposition_identifiers: str) -> DepositionMeta:
172
+ self.validate_deposition(deposition_identifiers)
173
+ return self.depositions[deposition_identifiers]
174
+
175
+ def write_material(self, material_meta: MaterialMeta, path: str | None = None):
176
+ path = self.get_benchmark_dir_path(path, os.path.join(BenchmarkData.MATERIAL_DIR, material_meta.identifier))
177
+
178
+ if not os.path.exists(path):
179
+ self.logger.debug(f'Creating directory "{path}"')
180
+ os.makedirs(path)
181
+
182
+ material_meta_file = os.path.join(path, BenchmarkData.META_JSON)
183
+ self.logger.debug(f'Writing material meta to "{material_meta_file}"')
184
+ with open(material_meta_file, "w") as file:
185
+ json.dump(
186
+ material_meta.to_dict(),
187
+ file,
188
+ indent=4,
189
+ )
190
+
191
+ write_data_file(material_meta.get_material().data, os.path.join(path, BenchmarkData.DATA_CSV))
192
+ prediction = material_meta.get_prediction()
193
+ if prediction is not None:
194
+ write_data_file(prediction.data, os.path.join(path, BenchmarkData.PREDICTION_CSV))
195
+
196
+ def write_deposition(self, deposition_meta: DepositionMeta, path: str | None = None):
197
+ path = self.get_benchmark_dir_path(path, os.path.join(BenchmarkData.DEPOSITION_DIR, deposition_meta.identifier))
198
+ if not os.path.exists(path):
199
+ self.logger.debug(f'Creating directory "{path}"')
200
+ os.makedirs(path)
201
+
202
+ deposition_meta_file = os.path.join(path, BenchmarkData.META_JSON)
203
+ self.logger.debug(f'Writing deposition meta to "{deposition_meta_file}"')
204
+ with open(deposition_meta_file, "w") as file:
205
+ json.dump(
206
+ deposition_meta.to_dict(),
207
+ file,
208
+ indent=4,
209
+ )
210
+
211
+ write_data_file(deposition_meta.get_deposition().data, os.path.join(path, BenchmarkData.DATA_CSV))