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:
yexiaozhou
2026-04-02 13:48:34 +08:00
parent f4c0e40a25
commit b04dc8dd4a
3 changed files with 63 additions and 4 deletions

View File

@@ -368,6 +368,7 @@ class OptimizeRequest(BaseModel):
workflow_edges: list[list[str]] = []
maxiter: int = 200
seed: int | None = None
snap_cardinal: bool = False
class PositionXYZ(BaseModel):
@@ -459,8 +460,8 @@ async def run_optimize(request: OptimizeRequest):
params={"mode": orientation_mode},
weight=request.seeder_overrides.get("orientation_weight", DEFAULT_WEIGHT_ANGLE),
))
# prefer_aligned: penalize non-cardinal angles
align_weight = request.seeder_overrides.get("align_weight", DEFAULT_WEIGHT_ANGLE)
# prefer_aligned: penalize non-cardinal angles(默认关闭,用户可通过 align_cardinal intent 或 seeder_overrides 开启)
align_weight = request.seeder_overrides.get("align_weight", 0)
if align_weight > 0:
constraints.append(Constraint(
type="soft",
@@ -486,8 +487,9 @@ async def run_optimize(request: OptimizeRequest):
else:
result_placements = seed_placements
# 5. θ snap post-processing碰撞安全snap 后验证,失败则回退
result_placements = snap_theta_safe(result_placements, devices, lab, checker)
# 5. θ snap post-processingopt-in默认关闭
if request.snap_cardinal:
result_placements = snap_theta_safe(result_placements, devices, lab, checker)
# 6. Evaluate final cost (binary mode for pass/fail reporting)
final_cost = evaluate_default_hard_constraints(

View File

@@ -376,3 +376,58 @@ class TestScenarios:
assert not _has_collision(devices, result)
for i, p in enumerate(result):
assert _facing_dot(p, devices[i], lab) > 0
# ── V2 Stage 1: 默认关闭 cardinal snap/alignment ────────
class TestV2Stage1Bugfixes:
"""align_weight 默认为 0snap_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

View File

@@ -199,6 +199,8 @@ class TestStage3VerifyPlacements:
"run_de": True,
"maxiter": 100,
"seed": 42,
"snap_cardinal": True,
"seeder_overrides": {"align_weight": 60},
})
data = optimize_resp.json()
assert data["success"] is True