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.
@@ -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 — we don't know sim names without state,
161
- # so just glob for anything matching the pattern
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 = f"{self.base_simulation.simulation_name}_sim{sim_index}"
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
- {"field": "max_time", "values": [0.02, 0.04]}
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.13.dev0
3
+ Version: 0.13.dev2
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -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=9GL67vCpIJziI9D9BpFtiYfDm4EZWpdh867TuF6GppM,14489
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=AXQSuXXglQtuPFfIt5KBtKzFc7oMm7shq3FdJcwM7OI,16157
16
- metafold-0.13.dev0.dist-info/licenses/LICENSE,sha256=LejZXzGwe9t0Ezk6g0bRmWUuFSI9vQSk1wJAc1NrrhE,1059
17
- metafold-0.13.dev0.dist-info/METADATA,sha256=pLhUoy9hHFp8znIT-_qNt5KuTBIJuZOWOCKEnuf6g7I,3495
18
- metafold-0.13.dev0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
19
- metafold-0.13.dev0.dist-info/top_level.txt,sha256=0dvwa2N6gvl2x4T9c62V4MbYD2soFedI6hm3-lWgyew,9
20
- metafold-0.13.dev0.dist-info/RECORD,,
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,,