mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-24 16:10:00 +00:00
feat(layout_optimizer): default cardinal snap and alignment to off
align_weight defaults to 0 (was DEFAULT_WEIGHT_ANGLE=60). snap_theta_safe is opt-in via snap_cardinal=True (was always-on). Both remain available when explicitly requested. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -368,6 +368,7 @@ class OptimizeRequest(BaseModel):
|
|||||||
workflow_edges: list[list[str]] = []
|
workflow_edges: list[list[str]] = []
|
||||||
maxiter: int = 200
|
maxiter: int = 200
|
||||||
seed: int | None = None
|
seed: int | None = None
|
||||||
|
snap_cardinal: bool = False
|
||||||
|
|
||||||
|
|
||||||
class PositionXYZ(BaseModel):
|
class PositionXYZ(BaseModel):
|
||||||
@@ -459,8 +460,8 @@ async def run_optimize(request: OptimizeRequest):
|
|||||||
params={"mode": orientation_mode},
|
params={"mode": orientation_mode},
|
||||||
weight=request.seeder_overrides.get("orientation_weight", DEFAULT_WEIGHT_ANGLE),
|
weight=request.seeder_overrides.get("orientation_weight", DEFAULT_WEIGHT_ANGLE),
|
||||||
))
|
))
|
||||||
# prefer_aligned: penalize non-cardinal angles
|
# prefer_aligned: penalize non-cardinal angles(默认关闭,用户可通过 align_cardinal intent 或 seeder_overrides 开启)
|
||||||
align_weight = request.seeder_overrides.get("align_weight", DEFAULT_WEIGHT_ANGLE)
|
align_weight = request.seeder_overrides.get("align_weight", 0)
|
||||||
if align_weight > 0:
|
if align_weight > 0:
|
||||||
constraints.append(Constraint(
|
constraints.append(Constraint(
|
||||||
type="soft",
|
type="soft",
|
||||||
@@ -486,8 +487,9 @@ async def run_optimize(request: OptimizeRequest):
|
|||||||
else:
|
else:
|
||||||
result_placements = seed_placements
|
result_placements = seed_placements
|
||||||
|
|
||||||
# 5. θ snap post-processing(碰撞安全:snap 后验证,失败则回退)
|
# 5. θ snap post-processing(opt-in,默认关闭)
|
||||||
result_placements = snap_theta_safe(result_placements, devices, lab, checker)
|
if request.snap_cardinal:
|
||||||
|
result_placements = snap_theta_safe(result_placements, devices, lab, checker)
|
||||||
|
|
||||||
# 6. Evaluate final cost (binary mode for pass/fail reporting)
|
# 6. Evaluate final cost (binary mode for pass/fail reporting)
|
||||||
final_cost = evaluate_default_hard_constraints(
|
final_cost = evaluate_default_hard_constraints(
|
||||||
|
|||||||
@@ -376,3 +376,58 @@ class TestScenarios:
|
|||||||
assert not _has_collision(devices, result)
|
assert not _has_collision(devices, result)
|
||||||
for i, p in enumerate(result):
|
for i, p in enumerate(result):
|
||||||
assert _facing_dot(p, devices[i], lab) > 0
|
assert _facing_dot(p, devices[i], lab) > 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── V2 Stage 1: 默认关闭 cardinal snap/alignment ────────
|
||||||
|
|
||||||
|
class TestV2Stage1Bugfixes:
|
||||||
|
"""align_weight 默认为 0,snap_cardinal 默认关闭。"""
|
||||||
|
|
||||||
|
def test_default_align_weight_is_zero(self):
|
||||||
|
"""Default request (no seeder_overrides) should NOT inject prefer_aligned."""
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from ..server import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
resp = client.post("/optimize", json={
|
||||||
|
"devices": [{"id": "opentrons_liquid_handler", "uuid": "u1"}],
|
||||||
|
"lab": {"width": 3, "depth": 3},
|
||||||
|
"seeder": "compact_outward",
|
||||||
|
"run_de": True,
|
||||||
|
"maxiter": 50,
|
||||||
|
"seed": 42,
|
||||||
|
})
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def test_snap_cardinal_off_by_default(self):
|
||||||
|
"""Default request should NOT snap theta to cardinal."""
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from ..server import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
resp = client.post("/optimize", json={
|
||||||
|
"devices": [{"id": "opentrons_liquid_handler", "uuid": "u1"}],
|
||||||
|
"lab": {"width": 3, "depth": 3},
|
||||||
|
"seeder": "compact_outward",
|
||||||
|
"run_de": True,
|
||||||
|
"maxiter": 10,
|
||||||
|
"seed": 42,
|
||||||
|
})
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def test_snap_cardinal_opt_in(self):
|
||||||
|
"""snap_cardinal=True should be accepted and snap angles."""
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from ..server import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
resp = client.post("/optimize", json={
|
||||||
|
"devices": [{"id": "opentrons_liquid_handler", "uuid": "u1"}],
|
||||||
|
"lab": {"width": 3, "depth": 3},
|
||||||
|
"seeder": "compact_outward",
|
||||||
|
"snap_cardinal": True,
|
||||||
|
"run_de": True,
|
||||||
|
"maxiter": 10,
|
||||||
|
"seed": 42,
|
||||||
|
})
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ class TestStage3VerifyPlacements:
|
|||||||
"run_de": True,
|
"run_de": True,
|
||||||
"maxiter": 100,
|
"maxiter": 100,
|
||||||
"seed": 42,
|
"seed": 42,
|
||||||
|
"snap_cardinal": True,
|
||||||
|
"seeder_overrides": {"align_weight": 60},
|
||||||
})
|
})
|
||||||
data = optimize_resp.json()
|
data = optimize_resp.json()
|
||||||
assert data["success"] is True
|
assert data["success"] is True
|
||||||
|
|||||||
Reference in New Issue
Block a user