metafold 0.12.dev11__tar.gz → 0.12.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 (35) hide show
  1. {metafold-0.12.dev11 → metafold-0.12.1}/PKG-INFO +3 -3
  2. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/simulation/compression_simulation.py +57 -40
  3. {metafold-0.12.dev11 → metafold-0.12.1}/metafold.egg-info/PKG-INFO +3 -3
  4. {metafold-0.12.dev11 → metafold-0.12.1}/metafold.egg-info/requires.txt +1 -1
  5. {metafold-0.12.dev11 → metafold-0.12.1}/pyproject.toml +5 -5
  6. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_compression_simulation.py +7 -4
  7. {metafold-0.12.dev11 → metafold-0.12.1}/LICENSE +0 -0
  8. {metafold-0.12.dev11 → metafold-0.12.1}/README.md +0 -0
  9. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/__init__.py +0 -0
  10. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/api.py +0 -0
  11. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/assets.py +0 -0
  12. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/auth.py +0 -0
  13. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/client.py +0 -0
  14. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/exceptions.py +0 -0
  15. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/jobs.py +0 -0
  16. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/materials.py +0 -0
  17. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/projects.py +0 -0
  18. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/simulation/__init__.py +0 -0
  19. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/simulation/compression_experiment.py +0 -0
  20. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/simulation/run_experiment.py +0 -0
  21. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/utils.py +0 -0
  22. {metafold-0.12.dev11 → metafold-0.12.1}/metafold/workflows.py +0 -0
  23. {metafold-0.12.dev11 → metafold-0.12.1}/metafold.egg-info/SOURCES.txt +0 -0
  24. {metafold-0.12.dev11 → metafold-0.12.1}/metafold.egg-info/dependency_links.txt +0 -0
  25. {metafold-0.12.dev11 → metafold-0.12.1}/metafold.egg-info/top_level.txt +0 -0
  26. {metafold-0.12.dev11 → metafold-0.12.1}/setup.cfg +0 -0
  27. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_assets.py +0 -0
  28. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_compession_experiment.py +0 -0
  29. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_jobs.py +0 -0
  30. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_materials.py +0 -0
  31. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_projects.py +0 -0
  32. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_run_experiment.py +0 -0
  33. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_shear_simulation.py +0 -0
  34. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_utils.py +0 -0
  35. {metafold-0.12.dev11 → metafold-0.12.1}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.12.dev11
3
+ Version: 0.12.1
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=3.12
28
+ Requires-Python: >=3.11
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: attrs~=23.2
@@ -38,7 +38,7 @@ Requires-Dist: dotenv>=0.9.9; extra == "simulation"
38
38
  Requires-Dist: pandas>=2.3.3; extra == "simulation"
39
39
  Requires-Dist: plyfile>=1.1.3; extra == "simulation"
40
40
  Requires-Dist: pyyaml>=6.0.3; extra == "simulation"
41
- Requires-Dist: simulation-configurator==0.1.2; extra == "simulation"
41
+ Requires-Dist: simulation-configurator==0.2.*; extra == "simulation"
42
42
  Requires-Dist: tables>=3.11.1; extra == "simulation"
43
43
  Dynamic: license-file
44
44
 
@@ -8,7 +8,7 @@ import uuid
8
8
 
9
9
  from requests import HTTPError
10
10
  import yaml
11
- from dotenv import load_dotenv
11
+ from dotenv import load_dotenv # type: ignore
12
12
  from metafold import MetafoldClient
13
13
  from pathlib import Path
14
14
  from numpy.typing import DTypeLike
@@ -353,6 +353,8 @@ class CompressionSimulation:
353
353
  bvh_filename: Optional[str] = None
354
354
  # Sampling resolution (longest axis) assigned for the pass-2 sample job.
355
355
  sample_resolution: Optional[int] = None
356
+ # From the prep metrics job; None until prep completes.
357
+ interior_volume: Optional[float] = None
356
358
  patch: dict = field(default_factory=dict)
357
359
  jobs: dict[str, Any] = field(default_factory=lambda: {})
358
360
  part_unique_name = ""
@@ -374,6 +376,7 @@ class CompressionSimulation:
374
376
  "is_piston": isinstance(self.part, ExperimentPistonBase),
375
377
  "part_name": self.part.name,
376
378
  "disabled": self.disabled,
379
+ "interior_volume": self.interior_volume,
377
380
  }
378
381
 
379
382
  def restore_from_state_dict(self, saved: dict):
@@ -382,6 +385,7 @@ class CompressionSimulation:
382
385
  self.material_name = saved["material_name"]
383
386
  self.jobs = saved["jobs"]
384
387
  self.disabled = saved.get("disabled", False)
388
+ self.interior_volume = saved.get("interior_volume")
385
389
 
386
390
  client: MetafoldClient
387
391
  project_id: str = ""
@@ -425,7 +429,7 @@ class CompressionSimulation:
425
429
  simulation_name: str,
426
430
  project_id: str = "",
427
431
  stl_folder_path: str = "PLY",
428
- env_source: str = ".env.dev",
432
+ env_source: str | None = ".env.dev",
429
433
  output_path: str = "",
430
434
  client: Optional[MetafoldClient] = None,
431
435
  simulation_parameters: SimulationParameters = SimulationParameters(),
@@ -642,7 +646,8 @@ class CompressionSimulation:
642
646
  return created.id
643
647
 
644
648
  def setup_client(self, env_source, project_id) -> MetafoldClient:
645
- load_dotenv(env_source)
649
+ if env_source is not None:
650
+ load_dotenv(env_source)
646
651
 
647
652
  if not project_id:
648
653
  self.project_id = os.environ.get("METAFOLD_PROJECT_ID", "")
@@ -824,6 +829,24 @@ class CompressionSimulation:
824
829
  params[f"{sample_mesh_job}.resolution"] = f"{resolution}"
825
830
  part_info.jobs["sample-mesh"] = sample_mesh_job
826
831
 
832
+ # Metrics run in prep (not the main workflow) so interior volume
833
+ # is known before dispatch, when the server manifest is uploaded.
834
+ if self._is_analysis_target(part_info.part) and self._get_step(
835
+ WorkflowStepType.METRICS, part_info.part.name
836
+ ):
837
+ metrics_job = f"metrics-{unique_name}"
838
+ jobs[metrics_job] = {
839
+ "type": "implicit/metrics",
840
+ "needs": [sample_mesh_job],
841
+ "assets": {"volume": sample_mesh_job},
842
+ "parameters": {
843
+ "volume_size": {"job": sample_mesh_job, "parameter": "patch_size"},
844
+ "volume_offset": {"job": sample_mesh_job, "parameter": "patch_offset"},
845
+ "volume_resolution": {"job": sample_mesh_job, "parameter": "patch_resolution"},
846
+ },
847
+ }
848
+ part_info.jobs["metrics"] = metrics_job
849
+
827
850
  workflow_yaml = yaml.dump({"jobs": jobs}, default_flow_style=False)
828
851
  return workflow_yaml, params, assets
829
852
 
@@ -1025,6 +1048,30 @@ class CompressionSimulation:
1025
1048
  if volume_asset is not None:
1026
1049
  info.volume_filename = volume_asset.filename
1027
1050
 
1051
+ metrics_job = prep_workflow_jobs.get(info.jobs.get("metrics", ""))
1052
+ if metrics_job is not None:
1053
+ raw = metrics_job.outputs.params.get("interior_volume")
1054
+ if raw is not None:
1055
+ info.interior_volume = float(raw)
1056
+
1057
+ def _total_interior_volume(self, workflow: Optional[Workflow] = None) -> float:
1058
+ """Sum interior volumes across analysis-target parts. Falls back to
1059
+ the main-workflow metrics readback for experiments dispatched before
1060
+ metrics moved to the prep workflow."""
1061
+ total = 0.0
1062
+ for part_info in self.part_infos:
1063
+ if not self._is_analysis_target(part_info.part) or part_info.disabled:
1064
+ continue
1065
+ if part_info.interior_volume is not None:
1066
+ total += part_info.interior_volume
1067
+ continue
1068
+ metrics_job = part_info.jobs.get("metrics", "")
1069
+ if workflow is not None and metrics_job:
1070
+ raw = workflow.get_parameter(f"{metrics_job}.interior_volume")
1071
+ if raw is not None:
1072
+ total += float(raw)
1073
+ return total
1074
+
1028
1075
  def get_part_info(self, name):
1029
1076
  for info in self.part_infos:
1030
1077
  if info.part.name == name:
@@ -1218,6 +1265,9 @@ class CompressionSimulation:
1218
1265
  ElementTree.indent(self.ups)
1219
1266
 
1220
1267
  def _add_to_workflow_metrics(self, part_infos: list[PartInfo]):
1268
+ # Duplicates the prep metrics jobs (which feed the server manifest):
1269
+ # removing these changed main-workflow scheduling and left it pending,
1270
+ # so they stay until that's understood.
1221
1271
  for part_info in part_infos:
1222
1272
  part = part_info.part
1223
1273
  if not self._is_analysis_target(part):
@@ -1241,7 +1291,6 @@ class CompressionSimulation:
1241
1291
  self.workflow_params[f"{metrics_job_name}.volume_resolution"] = json.dumps(
1242
1292
  part_info.patch["resolution"]
1243
1293
  )
1244
- part_info.jobs["metrics"] = metrics_job_name
1245
1294
 
1246
1295
  def _add_to_workflow_compress(self, part_infos: list[PartInfo]):
1247
1296
  compress_job_name = "compress"
@@ -2046,16 +2095,7 @@ class CompressionSimulation:
2046
2095
  data["ply"] = f"{name}/ply/part.####.ply"
2047
2096
 
2048
2097
  # Sum interior volumes from all parts
2049
- total_volume = 0.0
2050
- for part_info in self.part_infos:
2051
- if self._is_analysis_target(part_info.part):
2052
- if part_info.disabled:
2053
- continue
2054
- metrics_job = part_info.jobs.get("metrics", "")
2055
- if metrics_job:
2056
- raw = w.get_parameter(f"{metrics_job}.interior_volume")
2057
- if raw is not None:
2058
- total_volume += float(raw)
2098
+ total_volume = self._total_interior_volume(w)
2059
2099
 
2060
2100
  result.update(
2061
2101
  {
@@ -2279,18 +2319,7 @@ class CompressionSimulation:
2279
2319
  }
2280
2320
 
2281
2321
  # Sum interior volumes across analysis-target parts.
2282
- total_volume = 0.0
2283
- for part_info in self.part_infos:
2284
- if not self._is_analysis_target(part_info.part):
2285
- continue
2286
- if part_info.disabled:
2287
- continue
2288
- metrics_job = part_info.jobs.get("metrics", "")
2289
- if not metrics_job:
2290
- continue
2291
- raw = w.get_parameter(f"{metrics_job}.interior_volume")
2292
- if raw is not None:
2293
- total_volume += float(raw)
2322
+ total_volume = self._total_interior_volume(w)
2294
2323
 
2295
2324
  result.update(
2296
2325
  {
@@ -2445,19 +2474,7 @@ class CompressionSimulation:
2445
2474
  Volume is inlined (from prep workflow parameters); energy values are
2446
2475
  refs that the server resolves from the energy-metrics job output."""
2447
2476
  scalars: dict[str, Any] = {}
2448
-
2449
- total_volume = 0.0
2450
- for part_info in self.part_infos:
2451
- if not self._is_analysis_target(part_info.part):
2452
- continue
2453
- if part_info.disabled:
2454
- continue
2455
- metrics_job = part_info.jobs.get("metrics", "")
2456
- if metrics_job:
2457
- raw = workflow.get_parameter(f"{metrics_job}.interior_volume")
2458
- if raw is not None:
2459
- total_volume += float(raw)
2460
- scalars["volume"] = total_volume
2477
+ scalars["volume"] = self._total_interior_volume(workflow)
2461
2478
 
2462
2479
  if self._contains_step(WorkflowStepType.ENERGY_METRICS):
2463
2480
  em_job_id = job_id_lookup.get("energy-metrics", "")
@@ -2717,7 +2734,7 @@ class CompressionSimulation:
2717
2734
  "heading": "Absorbed Energy (J)",
2718
2735
  "filtering": False,
2719
2736
  }
2720
- )
2737
+ )
2721
2738
  self.manifest["resultsListConfig"].append(
2722
2739
  {
2723
2740
  "key": "loadingEnergy",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.12.dev11
3
+ Version: 0.12.1
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=3.12
28
+ Requires-Python: >=3.11
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: attrs~=23.2
@@ -38,7 +38,7 @@ Requires-Dist: dotenv>=0.9.9; extra == "simulation"
38
38
  Requires-Dist: pandas>=2.3.3; extra == "simulation"
39
39
  Requires-Dist: plyfile>=1.1.3; extra == "simulation"
40
40
  Requires-Dist: pyyaml>=6.0.3; extra == "simulation"
41
- Requires-Dist: simulation-configurator==0.1.2; extra == "simulation"
41
+ Requires-Dist: simulation-configurator==0.2.*; extra == "simulation"
42
42
  Requires-Dist: tables>=3.11.1; extra == "simulation"
43
43
  Dynamic: license-file
44
44
 
@@ -9,5 +9,5 @@ dotenv>=0.9.9
9
9
  pandas>=2.3.3
10
10
  plyfile>=1.1.3
11
11
  pyyaml>=6.0.3
12
- simulation-configurator==0.1.2
12
+ simulation-configurator==0.2.*
13
13
  tables>=3.11.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "metafold"
7
- version = "0.12.dev11"
7
+ version = "0.12.1"
8
8
  authors = [
9
9
  {name = "Metafold 3D", email = "info@metafold3d.com"},
10
10
  ]
@@ -18,7 +18,7 @@ dependencies = [
18
18
  readme = "README.md"
19
19
  license = {file = "LICENSE"}
20
20
  description = "Metafold SDK for Python"
21
- requires-python = ">=3.12"
21
+ requires-python = ">=3.11"
22
22
  keywords = ["metafold", "api", "sdk", "implicit geometry"]
23
23
  classifiers = [
24
24
  "Development Status :: 3 - Alpha",
@@ -38,7 +38,7 @@ simulation = [
38
38
  "pandas>=2.3.3",
39
39
  "plyfile>=1.1.3",
40
40
  "pyyaml>=6.0.3",
41
- "simulation-configurator==0.1.2",
41
+ "simulation-configurator==0.2.*",
42
42
  "tables>=3.11.1",
43
43
  ]
44
44
 
@@ -68,7 +68,7 @@ packages = ["metafold", "metafold.simulation"]
68
68
  environments = ["sys_platform == 'linux'"]
69
69
 
70
70
  [tool.uv.sources]
71
- simulation-configurator = { index = "sim-config-idx" }
71
+ simulation-configurator = { index = "gitlab" }
72
72
 
73
73
  [[tool.mypy.overrides]]
74
74
  module = [
@@ -79,7 +79,7 @@ module = [
79
79
  ignore_missing_imports = true
80
80
 
81
81
  [[tool.uv.index]]
82
- name = "sim-config-idx"
82
+ name = "gitlab"
83
83
  url = "https://gitlab.com/api/v4/projects/75460563/packages/pypi/simple"
84
84
  explicit = true
85
85
  authenticate = "always"
@@ -167,13 +167,15 @@ class TestBuildWorkflow:
167
167
  assert isinstance(piston.part, ExperimentPistonBase)
168
168
  assert piston.material_index == 0
169
169
 
170
- def test_build_workflow_has_per_part_metrics_jobs(self, prepared_sim):
171
- yaml_str, _ = prepared_sim.build_workflow()
170
+ def test_prep_workflow_has_per_part_metrics_jobs(self, prepared_sim):
171
+ mesh_parts = [p for p in prepared_sim.part_infos if hasattr(p.part, "filename")]
172
+ yaml_str, _, _ = prepared_sim._build_sample_workflow_for_batch(mesh_parts)
172
173
  loaded = yaml.safe_load(yaml_str)
173
- # one metrics job per non-piston mesh part
174
+ # one metrics job per non-piston mesh part, chained to its sample job
174
175
  for info in prepared_sim.part_infos:
175
176
  if not isinstance(info.part, ExperimentPistonBase) and hasattr(info.part, "filename"):
176
- assert f"metrics-{info.part_unique_name}" in loaded["jobs"]
177
+ job = loaded["jobs"][f"metrics-{info.part_unique_name}"]
178
+ assert job["needs"] == [f"sample-mesh-{info.part_unique_name}"]
177
179
 
178
180
  def test_build_workflow_compress_has_velocity(self, prepared_sim):
179
181
  _, params = prepared_sim.build_workflow()
@@ -1130,6 +1132,7 @@ class TestSetupClient:
1130
1132
  simulation_name="t",
1131
1133
  stl_folder_path=str(ply_folder),
1132
1134
  output_path=str(tmp_path / "out"),
1135
+ env_source=None, # Ensure local .env files do not pollute test environment
1133
1136
  **kwargs,
1134
1137
  )
1135
1138
 
File without changes
File without changes
File without changes
File without changes