metafold 0.13.dev0__py3-none-any.whl → 0.13.dev2__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.
- metafold/simulation/compression_experiment.py +96 -4
- metafold/simulation/run_experiment.py +30 -2
- {metafold-0.13.dev0.dist-info → metafold-0.13.dev2.dist-info}/METADATA +1 -1
- {metafold-0.13.dev0.dist-info → metafold-0.13.dev2.dist-info}/RECORD +7 -7
- {metafold-0.13.dev0.dist-info → metafold-0.13.dev2.dist-info}/WHEEL +0 -0
- {metafold-0.13.dev0.dist-info → metafold-0.13.dev2.dist-info}/licenses/LICENSE +0 -0
- {metafold-0.13.dev0.dist-info → metafold-0.13.dev2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import json
|
|
3
|
-
from typing import Any, List, Union, cast
|
|
3
|
+
from typing import Any, List, Optional, Union, cast
|
|
4
4
|
import glob
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from metafold.simulation.compression_simulation import (
|
|
@@ -106,6 +106,41 @@ class VarySimulationParameter(ExperimentVarying):
|
|
|
106
106
|
setattr(obj, parts[-1], self.values[sim_index])
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
class VaryVelocity(ExperimentVarying):
|
|
110
|
+
"""Vary a rigid part's velocity keyframes (e.g. a piston's motion profile).
|
|
111
|
+
Each value is a full velocity profile: a list of [t, vx, vy, vz] rows."""
|
|
112
|
+
|
|
113
|
+
part_name: str
|
|
114
|
+
velocities: List[list]
|
|
115
|
+
|
|
116
|
+
def __init__(self, part_name: str, velocities: List[list]):
|
|
117
|
+
# Each element is one profile: a list of [t,vx,vy,vz] rows.
|
|
118
|
+
for i, profile in enumerate(velocities):
|
|
119
|
+
if (
|
|
120
|
+
not isinstance(profile, list)
|
|
121
|
+
or not profile
|
|
122
|
+
or not all(isinstance(row, list) and len(row) == 4 for row in profile)
|
|
123
|
+
):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"velocity profile {i} for part {part_name!r} must be a list "
|
|
126
|
+
f"of [t, vx, vy, vz] rows, got: {profile!r}"
|
|
127
|
+
)
|
|
128
|
+
self.part_name = part_name
|
|
129
|
+
self.velocities = velocities
|
|
130
|
+
|
|
131
|
+
def resolve(self, _base_dir: Path) -> None:
|
|
132
|
+
self.sim_count = len(self.velocities)
|
|
133
|
+
|
|
134
|
+
def apply_to(self, sim_index: int, sim: CompressionSimulation):
|
|
135
|
+
part_info: CompressionSimulation.PartInfo = sim.get_part_info(self.part_name)
|
|
136
|
+
if not hasattr(part_info.part, "velocity"):
|
|
137
|
+
# Only rigid (piston/support) parts consume a velocity profile.
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"Part {self.part_name!r} is not a rigid (piston/support) part "
|
|
140
|
+
)
|
|
141
|
+
part_info.part.velocity = self.velocities[sim_index]
|
|
142
|
+
|
|
143
|
+
|
|
109
144
|
class CompressionExperiment:
|
|
110
145
|
base_simulation: CompressionSimulation
|
|
111
146
|
varying: List[ExperimentVarying] = []
|
|
@@ -124,12 +159,16 @@ class CompressionExperiment:
|
|
|
124
159
|
auto_upload_server_manifest: bool = True,
|
|
125
160
|
use_legacy_results_format: bool = False,
|
|
126
161
|
write_ups: bool = True,
|
|
162
|
+
simulation_names: Optional[List[str]] = None,
|
|
127
163
|
):
|
|
128
164
|
simulation.use_legacy_results_format = use_legacy_results_format
|
|
129
165
|
simulation.write_ups = write_ups
|
|
130
166
|
self.use_legacy_results_format = use_legacy_results_format
|
|
131
167
|
self.base_simulation = simulation
|
|
132
168
|
self.varying = varying
|
|
169
|
+
# Optional per-simulation display names (index-aligned). A blank/missing
|
|
170
|
+
# entry falls back to the auto "<base>_sim<index>" name.
|
|
171
|
+
self.simulation_names = simulation_names or []
|
|
133
172
|
self.verbose = verbose
|
|
134
173
|
self.force_rerun = force_rerun
|
|
135
174
|
self.varying_part_names = []
|
|
@@ -139,12 +178,59 @@ class CompressionExperiment:
|
|
|
139
178
|
if hasattr(v, "part_name"):
|
|
140
179
|
self.varying_part_names.append(v.part_name)
|
|
141
180
|
|
|
181
|
+
# Every varying must produce the same number of variants — prepare()
|
|
182
|
+
# sizes the sim loop from one of them and indexes into all of them.
|
|
183
|
+
counts = {v.sim_count for v in self.varying}
|
|
184
|
+
if len(counts) > 1:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
"All varying entries must have the same number of values, got "
|
|
187
|
+
f"counts: {sorted(counts)}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
self._validate_simulation_names()
|
|
191
|
+
|
|
142
192
|
if auto_run:
|
|
143
193
|
self.prepare()
|
|
144
194
|
self.run(auto_upload_server_manifest)
|
|
145
195
|
if auto_download_results:
|
|
146
196
|
self.download_results()
|
|
147
197
|
|
|
198
|
+
def _sim_name(self, sim_index: int) -> str:
|
|
199
|
+
"""Name for the sim at sim_index: the caller-supplied name when present,
|
|
200
|
+
otherwise the auto "<base>_sim<index>" name."""
|
|
201
|
+
custom = (
|
|
202
|
+
self.simulation_names[sim_index]
|
|
203
|
+
if sim_index < len(self.simulation_names)
|
|
204
|
+
else None
|
|
205
|
+
)
|
|
206
|
+
return custom or f"{self.base_simulation.simulation_name}_sim{sim_index}"
|
|
207
|
+
|
|
208
|
+
def _validate_simulation_names(self):
|
|
209
|
+
"""Names must be a list of path-safe strings (None or "" = use the
|
|
210
|
+
auto name) and resolve to a unique name per sim (they become filenames
|
|
211
|
+
and zip member paths)."""
|
|
212
|
+
if not isinstance(self.simulation_names, list) or not all(
|
|
213
|
+
n is None or isinstance(n, str) for n in self.simulation_names
|
|
214
|
+
):
|
|
215
|
+
raise ValueError(
|
|
216
|
+
f"simulation_names must be a list of strings, got: "
|
|
217
|
+
f"{self.simulation_names!r}"
|
|
218
|
+
)
|
|
219
|
+
self.simulation_names = [n or "" for n in self.simulation_names]
|
|
220
|
+
for name in self.simulation_names:
|
|
221
|
+
if any(c in name for c in "/\\:"):
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"simulation name {name!r} contains a path separator"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
sim_count = self.varying[0].sim_count if self.varying else 1
|
|
227
|
+
resolved = [self._sim_name(i) for i in range(sim_count)]
|
|
228
|
+
duplicates = {n for n in resolved if resolved.count(n) > 1}
|
|
229
|
+
if duplicates:
|
|
230
|
+
raise ValueError(
|
|
231
|
+
f"simulation names must be unique, duplicated: {sorted(duplicates)}"
|
|
232
|
+
)
|
|
233
|
+
|
|
148
234
|
@property
|
|
149
235
|
def _experiment_state_filename(self) -> Path:
|
|
150
236
|
assert self.base_simulation.out_dir is not None
|
|
@@ -157,12 +243,18 @@ class CompressionExperiment:
|
|
|
157
243
|
"""Delete the experiment state file and all per-sim results files."""
|
|
158
244
|
if self._experiment_state_filename.is_file():
|
|
159
245
|
self._experiment_state_filename.unlink()
|
|
160
|
-
# delete per-sim results
|
|
161
|
-
#
|
|
246
|
+
# delete per-sim results: auto-named sims match the glob pattern,
|
|
247
|
+
# custom-named sims are deleted by name
|
|
162
248
|
for f in self.base_simulation.out_dir.glob(
|
|
163
249
|
f"{self.base_simulation.simulation_name}_sim*_results.json"
|
|
164
250
|
):
|
|
165
251
|
f.unlink()
|
|
252
|
+
for name in self.simulation_names:
|
|
253
|
+
if not name:
|
|
254
|
+
continue
|
|
255
|
+
f = self.base_simulation.out_dir / f"{name}_results.json"
|
|
256
|
+
if f.is_file():
|
|
257
|
+
f.unlink()
|
|
166
258
|
# delete out.zip if it exists
|
|
167
259
|
out_zip = self.base_simulation.out_dir / "out.zip"
|
|
168
260
|
if out_zip.is_file():
|
|
@@ -193,7 +285,7 @@ class CompressionExperiment:
|
|
|
193
285
|
self.base_simulation.part_infos = base_part_infos
|
|
194
286
|
|
|
195
287
|
# give each sim a unique name so zip paths don't collide
|
|
196
|
-
clone.simulation_name =
|
|
288
|
+
clone.simulation_name = self._sim_name(sim_index)
|
|
197
289
|
|
|
198
290
|
# ok now for the part infos we want to do something different, we want to keep shared elements the same but fork
|
|
199
291
|
# the part infos which we are varying, that way we dont mess up other copies when experiments vary things
|
|
@@ -55,7 +55,14 @@ JSON manifest format
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
]},
|
|
58
|
-
|
|
58
|
+
# A rigid part's velocity keyframes; each value is a full [t,vx,vy,vz] profile:
|
|
59
|
+
{"part": "piston", "velocity": [
|
|
60
|
+
[[0.0, 0, 0, 0.0], [0.04, 0, 0, -1.25]],
|
|
61
|
+
[[0.0, 0, 0, 0.0], [0.04, 0, 0, -2.50]],
|
|
62
|
+
[[0.0, 0, 0, 0.0], [0.04, 0, 0, -5.00]]
|
|
63
|
+
]},
|
|
64
|
+
# Every varying entry must have the same number of values (one per variant).
|
|
65
|
+
{"field": "max_time", "values": [0.02, 0.04, 0.06]}
|
|
59
66
|
],
|
|
60
67
|
|
|
61
68
|
"simulation": {
|
|
@@ -82,7 +89,12 @@ JSON manifest format
|
|
|
82
89
|
"compress",
|
|
83
90
|
"force_displacement",
|
|
84
91
|
{"step": "metrics", "parts": ["midsole", "outsole"]}
|
|
85
|
-
]
|
|
92
|
+
],
|
|
93
|
+
|
|
94
|
+
# Optional display name per simulation variant, index-aligned with the variants
|
|
95
|
+
# the varying entries produce. A null/blank/missing entry falls back to
|
|
96
|
+
# "<name>_sim<i>". Names must be unique and must not contain path separators.
|
|
97
|
+
"simulation_names": ["slow piston", null, "fastest piston"]
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
Available material presets
|
|
@@ -137,6 +149,7 @@ from metafold.simulation.compression_experiment import (
|
|
|
137
149
|
VaryMaterial,
|
|
138
150
|
VaryMesh,
|
|
139
151
|
VarySimulationParameter,
|
|
152
|
+
VaryVelocity,
|
|
140
153
|
)
|
|
141
154
|
from metafold.simulation.compression_simulation import (
|
|
142
155
|
CompressionSimulation,
|
|
@@ -180,6 +193,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
180
193
|
|
|
181
194
|
if part_type == "piston_cylinder":
|
|
182
195
|
kwargs = {}
|
|
196
|
+
if "name" in entry:
|
|
197
|
+
kwargs["name"] = entry["name"]
|
|
183
198
|
if "velocity" in entry:
|
|
184
199
|
kwargs["velocity"] = entry["velocity"]
|
|
185
200
|
if "shape_parameters" in entry:
|
|
@@ -190,6 +205,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
190
205
|
|
|
191
206
|
elif part_type == "piston_box":
|
|
192
207
|
kwargs = {}
|
|
208
|
+
if "name" in entry:
|
|
209
|
+
kwargs["name"] = entry["name"]
|
|
193
210
|
if "velocity" in entry:
|
|
194
211
|
kwargs["velocity"] = entry["velocity"]
|
|
195
212
|
if "shape_parameters" in entry:
|
|
@@ -200,6 +217,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
200
217
|
|
|
201
218
|
elif part_type == "piston_mesh":
|
|
202
219
|
kwargs = {"filename": entry["file"]}
|
|
220
|
+
if "name" in entry:
|
|
221
|
+
kwargs["name"] = entry["name"]
|
|
203
222
|
if "velocity" in entry:
|
|
204
223
|
kwargs["velocity"] = entry["velocity"]
|
|
205
224
|
if "material" in entry:
|
|
@@ -243,6 +262,14 @@ def _build_varying(varying_config: list[dict]) -> list[ExperimentVarying]:
|
|
|
243
262
|
VaryMaterial(entry["part"], [_resolve_material(m) for m in materials])
|
|
244
263
|
)
|
|
245
264
|
|
|
265
|
+
elif "part" in entry and "velocity" in entry:
|
|
266
|
+
velocities = entry["velocity"]
|
|
267
|
+
if not isinstance(velocities, list):
|
|
268
|
+
raise ValueError(
|
|
269
|
+
f"varying 'velocity' must be a list of velocity profiles, got: {velocities!r}"
|
|
270
|
+
)
|
|
271
|
+
varying.append(VaryVelocity(entry["part"], velocities))
|
|
272
|
+
|
|
246
273
|
elif "field" in entry and "values" in entry:
|
|
247
274
|
varying.append(VarySimulationParameter(entry["field"], entry["values"]))
|
|
248
275
|
|
|
@@ -366,6 +393,7 @@ def run_experiment(
|
|
|
366
393
|
CompressionExperiment(
|
|
367
394
|
simulation=sim,
|
|
368
395
|
varying=varying,
|
|
396
|
+
simulation_names=config.get("simulation_names"),
|
|
369
397
|
auto_download_results=wait_for_results,
|
|
370
398
|
)
|
|
371
399
|
|
|
@@ -10,11 +10,11 @@ metafold/projects.py,sha256=4APFtJQjycFhlIHCWTrOAuU3CLX8drSlgPz8lhVXhzI,6295
|
|
|
10
10
|
metafold/utils.py,sha256=DnsW1Xr9mLpVHBNdPrdxNLLcjxSNMkWVvr7cH7UTAxY,1174
|
|
11
11
|
metafold/workflows.py,sha256=Mhv3n3ChUHw1Vaj3q8HNDbOgljn03AF6WslZfXzexsI,7802
|
|
12
12
|
metafold/simulation/__init__.py,sha256=tUW4PfyWnI6UFvIR__JKUD2FjNTM74fSeKuee7Ewjfo,816
|
|
13
|
-
metafold/simulation/compression_experiment.py,sha256=
|
|
13
|
+
metafold/simulation/compression_experiment.py,sha256=kLtX6V4vc_v2l-mJqpkE29TWE-fb6fCudOqhxv1Bz9U,18442
|
|
14
14
|
metafold/simulation/compression_simulation.py,sha256=pnAgadKWMj4Yexitz3wQRJsGbtV8iGxjdfXRe_rqJiM,118024
|
|
15
|
-
metafold/simulation/run_experiment.py,sha256=
|
|
16
|
-
metafold-0.13.
|
|
17
|
-
metafold-0.13.
|
|
18
|
-
metafold-0.13.
|
|
19
|
-
metafold-0.13.
|
|
20
|
-
metafold-0.13.
|
|
15
|
+
metafold/simulation/run_experiment.py,sha256=_TRsm6pHtHCLBlw8wqiN9WcvoAwmI0APPru-0Nj2QJw,17533
|
|
16
|
+
metafold-0.13.dev2.dist-info/licenses/LICENSE,sha256=LejZXzGwe9t0Ezk6g0bRmWUuFSI9vQSk1wJAc1NrrhE,1059
|
|
17
|
+
metafold-0.13.dev2.dist-info/METADATA,sha256=RaoMGGgcxBiSOf9AOWTI9cvA0_BbT6N5t9nN8Xm0LDk,3495
|
|
18
|
+
metafold-0.13.dev2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
19
|
+
metafold-0.13.dev2.dist-info/top_level.txt,sha256=0dvwa2N6gvl2x4T9c62V4MbYD2soFedI6hm3-lWgyew,9
|
|
20
|
+
metafold-0.13.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|