metafold 0.13.2.dev0__tar.gz → 0.14.dev0__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.
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/PKG-INFO +1 -1
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/compression_simulation.py +17 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/run_experiment.py +7 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/PKG-INFO +1 -1
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/pyproject.toml +1 -1
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_compression_simulation.py +24 -71
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/LICENSE +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/README.md +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/__init__.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/api.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/assets.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/auth.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/client.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/exceptions.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/jobs.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/materials.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/projects.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/__init__.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/compression_experiment.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/utils.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/workflows.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/SOURCES.txt +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/dependency_links.txt +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/requires.txt +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/top_level.txt +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/setup.cfg +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_assets.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_compession_experiment.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_jobs.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_materials.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_projects.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_run_experiment.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_shear_simulation.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_utils.py +0 -0
- {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_workflows.py +0 -0
|
@@ -245,12 +245,21 @@ class ExperimentParallelepiped(ExperimentPrimitive):
|
|
|
245
245
|
return pts.min(axis=0), pts.max(axis=0)
|
|
246
246
|
|
|
247
247
|
|
|
248
|
+
@dataclass
|
|
249
|
+
class ForceLimit:
|
|
250
|
+
face: str
|
|
251
|
+
value: float
|
|
252
|
+
|
|
253
|
+
|
|
248
254
|
@dataclass
|
|
249
255
|
class ExperimentPistonBase:
|
|
250
256
|
velocity: Optional[list] = field(default_factory=lambda: DEFAULT_PISTON_VELOCITY)
|
|
251
257
|
contact_type: str = "rigid"
|
|
252
258
|
mu: float = 0.0
|
|
253
259
|
|
|
260
|
+
force_limit: ForceLimit | None = None
|
|
261
|
+
"""Apply condition to jump to zero velocity when the given force is reached."""
|
|
262
|
+
|
|
254
263
|
|
|
255
264
|
@dataclass
|
|
256
265
|
class ExperimentPistonMesh(ExperimentMesh, ExperimentPistonBase):
|
|
@@ -1287,6 +1296,13 @@ class CompressionSimulation:
|
|
|
1287
1296
|
if info.disabled:
|
|
1288
1297
|
continue
|
|
1289
1298
|
if isinstance(info.part, (ExperimentPistonBase, ExperimentSupportBase)):
|
|
1299
|
+
kwargs = {}
|
|
1300
|
+
if isinstance(info.part, ExperimentPistonBase) and info.part.force_limit:
|
|
1301
|
+
kwargs.update({
|
|
1302
|
+
"condition": info.part.force_limit.face,
|
|
1303
|
+
"conditionValue": info.part.force_limit.value,
|
|
1304
|
+
"conditionAction": "GoToZeroVelocity",
|
|
1305
|
+
})
|
|
1290
1306
|
rigid_contacts.append(
|
|
1291
1307
|
Contact(
|
|
1292
1308
|
type=info.part.contact_type,
|
|
@@ -1295,6 +1311,7 @@ class CompressionSimulation:
|
|
|
1295
1311
|
master_material=info.material_index,
|
|
1296
1312
|
direction=[1, 1, 1],
|
|
1297
1313
|
materials=deformable_indices,
|
|
1314
|
+
**kwargs
|
|
1298
1315
|
)
|
|
1299
1316
|
)
|
|
1300
1317
|
|
|
@@ -158,6 +158,7 @@ from metafold.simulation.compression_simulation import (
|
|
|
158
158
|
ExperimentPistonBox,
|
|
159
159
|
ExperimentPistonCylinder,
|
|
160
160
|
ExperimentPistonMesh,
|
|
161
|
+
ForceLimit,
|
|
161
162
|
SimulationParameters,
|
|
162
163
|
WorkflowStep,
|
|
163
164
|
validate_simulation_parts,
|
|
@@ -202,6 +203,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
202
203
|
kwargs["shape_parameters"] = entry["shape_parameters"]
|
|
203
204
|
if "material" in entry:
|
|
204
205
|
kwargs["material"] = _resolve_material(entry["material"])
|
|
206
|
+
if "force_limit" in entry:
|
|
207
|
+
kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
|
|
205
208
|
parts.append(ExperimentPistonCylinder(**kwargs))
|
|
206
209
|
|
|
207
210
|
elif part_type == "piston_box":
|
|
@@ -214,6 +217,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
214
217
|
kwargs["shape_parameters"] = entry["shape_parameters"]
|
|
215
218
|
if "material" in entry:
|
|
216
219
|
kwargs["material"] = _resolve_material(entry["material"])
|
|
220
|
+
if "force_limit" in entry:
|
|
221
|
+
kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
|
|
217
222
|
parts.append(ExperimentPistonBox(**kwargs))
|
|
218
223
|
|
|
219
224
|
elif part_type == "piston_mesh":
|
|
@@ -224,6 +229,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
224
229
|
kwargs["velocity"] = entry["velocity"]
|
|
225
230
|
if "material" in entry:
|
|
226
231
|
kwargs["material"] = _resolve_material(entry["material"])
|
|
232
|
+
if "force_limit" in entry:
|
|
233
|
+
kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
|
|
227
234
|
parts.append(ExperimentPistonMesh(**kwargs))
|
|
228
235
|
|
|
229
236
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from io import BytesIO
|
|
2
|
+
from copy import deepcopy
|
|
2
3
|
from types import SimpleNamespace
|
|
3
4
|
from zipfile import ZipFile
|
|
4
5
|
|
|
@@ -16,6 +17,7 @@ from metafold.simulation.compression_simulation import (
|
|
|
16
17
|
ExperimentPistonCylinder,
|
|
17
18
|
ExperimentPistonMesh,
|
|
18
19
|
ExperimentSupportMesh,
|
|
20
|
+
ForceLimit,
|
|
19
21
|
SimulationParameters,
|
|
20
22
|
WorkflowStep,
|
|
21
23
|
WorkflowStepType,
|
|
@@ -725,6 +727,28 @@ jobs:
|
|
|
725
727
|
assert workflow_params[key] == value, f"mismatch for {key}: {workflow_params[key]!r} != {value!r}"
|
|
726
728
|
|
|
727
729
|
|
|
730
|
+
class TestSimConfig:
|
|
731
|
+
def test_force_limit(self, ply_folder, basic_parts, tmp_path):
|
|
732
|
+
parts = deepcopy(basic_parts)
|
|
733
|
+
parts[0].force_limit = ForceLimit("zminus", 2000)
|
|
734
|
+
sim = CompressionSimulation(
|
|
735
|
+
parts=parts,
|
|
736
|
+
simulation_name="test_sim",
|
|
737
|
+
stl_folder_path=str(ply_folder),
|
|
738
|
+
output_path=str(tmp_path / "out"),
|
|
739
|
+
client=MagicMock(),
|
|
740
|
+
)
|
|
741
|
+
sim.create_sim_config()
|
|
742
|
+
contact = sim.ups.findall("./MaterialProperties/MPM/contact")[0]
|
|
743
|
+
for tag, value in [
|
|
744
|
+
("condition", "zminus"),
|
|
745
|
+
("conditionValue", "2000"),
|
|
746
|
+
("conditionAction", "GoToZeroVelocity"),
|
|
747
|
+
]:
|
|
748
|
+
assert contact.find(tag) is not None
|
|
749
|
+
assert contact.find(tag).text == value
|
|
750
|
+
|
|
751
|
+
|
|
728
752
|
class TestStressStrainPerPart:
|
|
729
753
|
"""stress-strain runs once per deformable part (not one representative).
|
|
730
754
|
It is opt-in: callers must add the step to workflow_steps."""
|
|
@@ -1439,74 +1463,3 @@ class TestFindOrCreateProjectCancel:
|
|
|
1439
1463
|
|
|
1440
1464
|
assert pid == "new-pid"
|
|
1441
1465
|
client.workflows.cancel.assert_not_called()
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
class TestFindOrCreateProjectUserScope:
|
|
1445
|
-
"""With user set, only the caller's own project is reused; a same-named
|
|
1446
|
-
project owned by someone else (visible under org access) is ignored."""
|
|
1447
|
-
|
|
1448
|
-
@pytest.fixture
|
|
1449
|
-
def patched_client_class(self, monkeypatch):
|
|
1450
|
-
from metafold.simulation import compression_simulation
|
|
1451
|
-
fake_class = MagicMock()
|
|
1452
|
-
monkeypatch.setattr(compression_simulation, "MetafoldClient", fake_class)
|
|
1453
|
-
return fake_class
|
|
1454
|
-
|
|
1455
|
-
def _project(self, pid, name, user):
|
|
1456
|
-
p = MagicMock()
|
|
1457
|
-
p.id = pid
|
|
1458
|
-
p.name = name
|
|
1459
|
-
p.user = user
|
|
1460
|
-
return p
|
|
1461
|
-
|
|
1462
|
-
def test_ignores_same_name_owned_by_other_user(self, patched_client_class):
|
|
1463
|
-
client = patched_client_class.return_value
|
|
1464
|
-
# A defensive belt-and-suspenders case: even if the API returned another
|
|
1465
|
-
# user's project, the Python filter drops it and we create a new one.
|
|
1466
|
-
client.projects.list.return_value = [self._project("other-pid", "shared", "userB")]
|
|
1467
|
-
client.projects.create.return_value = self._project("new-pid", "shared", "userA")
|
|
1468
|
-
|
|
1469
|
-
pid = CompressionSimulation.find_or_create_project(
|
|
1470
|
-
"shared", access_token="tok", base_url="http://x/", user="userA",
|
|
1471
|
-
)
|
|
1472
|
-
|
|
1473
|
-
assert pid == "new-pid"
|
|
1474
|
-
client.projects.create.assert_called_once()
|
|
1475
|
-
|
|
1476
|
-
def test_reuses_callers_own_project(self, patched_client_class):
|
|
1477
|
-
client = patched_client_class.return_value
|
|
1478
|
-
client.projects.list.return_value = [self._project("mine", "shared", "userA")]
|
|
1479
|
-
|
|
1480
|
-
pid = CompressionSimulation.find_or_create_project(
|
|
1481
|
-
"shared", access_token="tok", base_url="http://x/", user="userA",
|
|
1482
|
-
cancel_existing_workflows=False,
|
|
1483
|
-
)
|
|
1484
|
-
|
|
1485
|
-
assert pid == "mine"
|
|
1486
|
-
client.projects.create.assert_not_called()
|
|
1487
|
-
|
|
1488
|
-
def test_scopes_query_to_user(self, patched_client_class):
|
|
1489
|
-
client = patched_client_class.return_value
|
|
1490
|
-
client.projects.list.return_value = []
|
|
1491
|
-
client.projects.create.return_value = self._project("new-pid", "shared", "userA")
|
|
1492
|
-
|
|
1493
|
-
CompressionSimulation.find_or_create_project(
|
|
1494
|
-
"shared", access_token="tok", base_url="http://x/", user="userA",
|
|
1495
|
-
)
|
|
1496
|
-
|
|
1497
|
-
q = client.projects.list.call_args.kwargs["q"]
|
|
1498
|
-
assert 'name:"shared"' in q
|
|
1499
|
-
assert 'user:"userA"' in q
|
|
1500
|
-
|
|
1501
|
-
def test_no_user_keeps_unscoped_behavior(self, patched_client_class):
|
|
1502
|
-
client = patched_client_class.return_value
|
|
1503
|
-
client.projects.list.return_value = [self._project("any-pid", "shared", "userB")]
|
|
1504
|
-
|
|
1505
|
-
pid = CompressionSimulation.find_or_create_project(
|
|
1506
|
-
"shared", access_token="tok", base_url="http://x/",
|
|
1507
|
-
cancel_existing_workflows=False,
|
|
1508
|
-
)
|
|
1509
|
-
|
|
1510
|
-
assert pid == "any-pid"
|
|
1511
|
-
q = client.projects.list.call_args.kwargs["q"]
|
|
1512
|
-
assert "user:" not in q
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|