The saved composite score previously came from a requirement-solve that only satisfied the platform's physical performance floor, not the domain's actual weighted score -- a smaller/cheaper build could always score higher by hand. _decide_masses now jointly searches platform, actuator, and storage mass (coarse-to-fine grid, no external deps) to maximize the domain's real weighted composite score, with the requirement floor as a lower bound rather than the final answer. Platform mass specifically was previously fixed at a geometric-mean representative value, which could be too little structure to carry its own required actuator+storage (reusing CARGO_KG_PER_STRUCTURAL_KG, the existing structure-carries-N-times-its-mass ratio, applied to a platform carrying its own powertrain instead of cargo). Growing platform mass also raises that structural ceiling, so it has to be searched jointly rather than fixed or bounded independently. Because power_density/range_fuel/cost_efficiency are all per-kg ratios, none of them naturally penalize a build whose absolute mass exceeds its own platform's declared ceiling -- a Piston Engine sized for a Hyperloop could still score well on a Light Personal Vehicle. Pass 2 now detects genuine infeasibility (no platform mass within its own declared ceiling can structurally carry the required floor) and saves it as a per-domain block instead of a misleadingly good score. Also adds an explore-panel warning (not a hard block, since exploration is intentionally loose) when a manually-dragged slider build exceeds the platform's mass ceiling or structural carrying capacity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""Tests for the multi-pass pipeline."""
|
|
|
|
from physcom.engine.constraint_resolver import ConstraintResolver
|
|
from physcom.engine.scorer import Scorer
|
|
from physcom.engine.pipeline import Pipeline
|
|
from physcom.llm.providers.mock import MockLLMProvider
|
|
|
|
|
|
def test_pass1_filters_impossible_combos(seeded_repo):
|
|
"""Pass 1 should block known-impossible combinations (e.g., solar sail + road vehicle)."""
|
|
domain = seeded_repo.get_domain("urban_commuting")
|
|
resolver = ConstraintResolver()
|
|
scorer = Scorer(domain)
|
|
pipeline = Pipeline(seeded_repo, resolver, scorer)
|
|
|
|
result = pipeline.run(domain, ["platform", "actuator", "energy_storage"], passes=[1])
|
|
|
|
from physcom.seed.transport_example import PLATFORMS, ACTUATORS, ENERGY_STORAGES
|
|
expected = len(PLATFORMS) * len(ACTUATORS) * len(ENERGY_STORAGES)
|
|
assert result.total_generated == expected
|
|
assert result.pass1_failed > 0
|
|
assert result.pass1_valid + result.pass1_conditional + result.pass1_failed == expected
|
|
|
|
|
|
def test_pass123_produces_scored_results(seeded_repo):
|
|
"""Passes 1-3 should produce a scored shortlist."""
|
|
domain = seeded_repo.get_domain("urban_commuting")
|
|
resolver = ConstraintResolver()
|
|
scorer = Scorer(domain)
|
|
pipeline = Pipeline(seeded_repo, resolver, scorer)
|
|
|
|
result = pipeline.run(
|
|
domain, ["platform", "actuator", "energy_storage"],
|
|
score_threshold=0.01, passes=[1, 2, 3, 5],
|
|
)
|
|
|
|
assert result.pass2_estimated > 0
|
|
assert result.pass3_above_threshold > 0
|
|
|
|
|
|
def test_pass4_with_mock_llm(seeded_repo):
|
|
"""Pass 4 should annotate with LLM review."""
|
|
domain = seeded_repo.get_domain("urban_commuting")
|
|
resolver = ConstraintResolver()
|
|
scorer = Scorer(domain)
|
|
mock_llm = MockLLMProvider(default_estimates={
|
|
"power_density": 500.0, "cost_efficiency": 5e-4, "safety": 0.6,
|
|
"availability": 0.7, "range_fuel": 200000.0,
|
|
})
|
|
pipeline = Pipeline(seeded_repo, resolver, scorer, llm=mock_llm)
|
|
|
|
result = pipeline.run(
|
|
domain, ["platform", "actuator", "energy_storage"],
|
|
score_threshold=0.01, passes=[1, 2, 3, 4, 5],
|
|
)
|
|
|
|
assert result.pass4_reviewed > 0
|
|
|
|
|
|
def test_blocked_combos_not_scored(seeded_repo):
|
|
"""Blocked combinations should not make it to scoring."""
|
|
domain = seeded_repo.get_domain("urban_commuting")
|
|
resolver = ConstraintResolver()
|
|
scorer = Scorer(domain)
|
|
pipeline = Pipeline(seeded_repo, resolver, scorer)
|
|
|
|
result = pipeline.run(
|
|
domain, ["platform", "actuator", "energy_storage"],
|
|
score_threshold=0.0, passes=[1, 2, 3, 5],
|
|
)
|
|
|
|
# Estimated count should be less than total (blocked ones filtered).
|
|
# Not necessarily equal to pass1_valid + pass1_conditional: a combo can
|
|
# pass pass 1's entity-declared-floor checks but still turn out
|
|
# structurally infeasible once pass 2 solves the domain-specific
|
|
# actuator/storage requirement (e.g. an engine too big to fit its own
|
|
# platform's declared mass ceiling) -- that's a legitimate per-domain
|
|
# block, not a bug (see Pipeline._decide_masses' `feasible` return).
|
|
assert result.pass2_estimated < result.total_generated
|
|
assert result.pass2_estimated <= result.pass1_valid + result.pass1_conditional
|