I love how stupid this project is
si units and redefining speed metric as thrust/weight ratio
This commit is contained in:
@@ -6,7 +6,10 @@
|
|||||||
"Bash(python -m pytest *)",
|
"Bash(python -m pytest *)",
|
||||||
"Bash(python -m physcom *)",
|
"Bash(python -m physcom *)",
|
||||||
"Bash(python -m physcom_web *)",
|
"Bash(python -m physcom_web *)",
|
||||||
"Bash(python -c *)"
|
"Bash(python -c *)",
|
||||||
|
"WebFetch(domain:127.0.0.1)",
|
||||||
|
"WebFetch(domain:localhost)",
|
||||||
|
"Bash(curl:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
LOGIC DOCS/001-speed-metric-to-power-density-scoring.md
Normal file
50
LOGIC DOCS/001-speed-metric-to-power-density-scoring.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Replace "speed" metric with power density scoring
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
After standardizing the database to SI base units, the stub estimator's speed formula was exposed as fundamentally broken:
|
||||||
|
|
||||||
|
```python
|
||||||
|
raw["speed"] = min(power_density * 0.14, 3e8) # m/s
|
||||||
|
```
|
||||||
|
|
||||||
|
An Electric Motor (2000 W/kg) on a Light Personal Vehicle produced 280 m/s (1008 km/h). The old formula had the same issue (1000 km/h) — it was just less visible in km/h with the `|si` filter masking the absurdity.
|
||||||
|
|
||||||
|
The root cause: **top speed cannot be derived from power density alone**. Speed depends on drag, rolling resistance, medium, vehicle geometry, and a dozen other factors the stub doesn't know. A linear power-to-speed model can never work across domains spanning e-bikes to spaceships.
|
||||||
|
|
||||||
|
## Candidate replacement: thrust-to-weight ratio (N/kg)
|
||||||
|
|
||||||
|
N/kg is dimensionally m/s² — a well-understood physical quantity. The actuator already declares `power_density` (W/kg), so the data exists. But three concerns emerged:
|
||||||
|
|
||||||
|
1. **Redundant with Pass 1** — The constraint system already checks actuator `power_density` against platform `power_density_required`. Combos that reach scoring have already passed this gate, so scoring on the same axis re-evaluates something the pipeline already filtered.
|
||||||
|
|
||||||
|
2. **Actuator-dominated** — Only the actuator declares `power_density`. Every combo sharing the same actuator scores identically, so the metric doesn't capture entity interplay.
|
||||||
|
|
||||||
|
3. **Higher isn't always better** — A Rocket Nozzle (10,000 W/kg) on a Light Personal Vehicle has extreme thrust-to-weight, but that's not desirable for urban commuting. The current scorer only does monotonic normalization (higher is better, or lower is better), not "closer to optimal band."
|
||||||
|
|
||||||
|
## The insight: `power_density_required` is a scoring concern, not a constraint
|
||||||
|
|
||||||
|
Examining what `power_density_required` actually represents on platforms:
|
||||||
|
|
||||||
|
- Road Vehicle: "I need >= 5 W/kg"
|
||||||
|
- Fixed-Wing Aircraft: "I need >= 100 W/kg"
|
||||||
|
- Spaceship: "I need >= 300 W/kg"
|
||||||
|
|
||||||
|
These thresholds are arbitrary performance gradients — a car with 4 W/kg still moves, just poorly. There's no hard physics cliff. Compare with genuinely binary constraints:
|
||||||
|
|
||||||
|
- **medium mutex** — a boat physically cannot operate in space
|
||||||
|
- **energy_form requires/provides** — an electric motor cannot burn gasoline
|
||||||
|
- **mass range** — a nuclear reactor won't fit on a bicycle frame
|
||||||
|
|
||||||
|
Power density fit is "how well does it work," not "can it work at all." That's scoring territory.
|
||||||
|
|
||||||
|
The one edge case — aircraft needing thrust-to-weight > 1 to leave the ground — is already implicitly handled by atmosphere/medium constraints and energy density minimums.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Remove `power_density_required` from platform entity constraints and introduce it as a domain-level scoring metric instead. This:
|
||||||
|
|
||||||
|
- Removes ~15 arbitrary constraint thresholds from seed data
|
||||||
|
- Lets more combos through to scoring for nuanced evaluation
|
||||||
|
- Creates a metric that varies by combo (actuator provides vs. platform context)
|
||||||
|
- Replaces the broken "speed" heuristic with something directly computable from entity data
|
||||||
5
LOGIC DOCS/README.md
Normal file
5
LOGIC DOCS/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Logic Docs
|
||||||
|
|
||||||
|
A series of thought experiments and design decisions made while developing PhysCom. Each document captures the reasoning behind a specific architectural or modeling choice — the "why" behind the code.
|
||||||
|
|
||||||
|
These are living records of the design process, not formal specifications. They preserve context that would otherwise be lost between development sessions.
|
||||||
@@ -41,7 +41,6 @@ class ConstraintResolver:
|
|||||||
self._check_requires_vs_excludes(all_deps, result)
|
self._check_requires_vs_excludes(all_deps, result)
|
||||||
self._check_mutual_exclusion(all_deps, result)
|
self._check_mutual_exclusion(all_deps, result)
|
||||||
self._check_range_incompatibility(all_deps, result)
|
self._check_range_incompatibility(all_deps, result)
|
||||||
self._check_power_density(combination, result)
|
|
||||||
self._check_energy_density(combination, result)
|
self._check_energy_density(combination, result)
|
||||||
self._check_unmet_requirements(all_deps, result)
|
self._check_unmet_requirements(all_deps, result)
|
||||||
|
|
||||||
@@ -125,35 +124,6 @@ class ConstraintResolver:
|
|||||||
f"but {max_name} limits {key} <= {max_val}"
|
f"but {max_name} limits {key} <= {max_val}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_power_density(
|
|
||||||
self, combination: Combination, result: ConstraintResult
|
|
||||||
) -> None:
|
|
||||||
"""Rule 4: If power source W/kg << platform required W/kg → warn/block."""
|
|
||||||
density_provided: list[tuple[str, float]] = []
|
|
||||||
density_required: list[tuple[str, float]] = []
|
|
||||||
|
|
||||||
for entity in combination.entities:
|
|
||||||
for dep in entity.dependencies:
|
|
||||||
if dep.key == "power_density_w_kg" and dep.constraint_type == "provides":
|
|
||||||
density_provided.append((entity.name, float(dep.value)))
|
|
||||||
elif dep.key == "power_density_required_w_kg" and dep.constraint_type == "range_min":
|
|
||||||
density_required.append((entity.name, float(dep.value)))
|
|
||||||
|
|
||||||
for req_name, req_density in density_required:
|
|
||||||
for prov_name, prov_density in density_provided:
|
|
||||||
if prov_density < req_density * 0.01:
|
|
||||||
result.violations.append(
|
|
||||||
f"{prov_name} provides {prov_density} W/kg but "
|
|
||||||
f"{req_name} requires {req_density} W/kg "
|
|
||||||
f"(power density deficit > 100x)"
|
|
||||||
)
|
|
||||||
elif prov_density < req_density:
|
|
||||||
result.warnings.append(
|
|
||||||
f"{prov_name} provides {prov_density} W/kg but "
|
|
||||||
f"{req_name} requires {req_density} W/kg "
|
|
||||||
f"(under-powered)"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_energy_density(
|
def _check_energy_density(
|
||||||
self, combination: Combination, result: ConstraintResult
|
self, combination: Combination, result: ConstraintResult
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -166,9 +136,9 @@ class ConstraintResolver:
|
|||||||
|
|
||||||
for entity in combination.entities:
|
for entity in combination.entities:
|
||||||
for dep in entity.dependencies:
|
for dep in entity.dependencies:
|
||||||
if dep.key == "energy_density_wh_kg" and dep.constraint_type == "provides":
|
if dep.key == "energy_density" and dep.constraint_type == "provides":
|
||||||
density_provided.append((entity.name, float(dep.value)))
|
density_provided.append((entity.name, float(dep.value)))
|
||||||
elif dep.key == "energy_density_wh_kg" and dep.constraint_type == "range_min":
|
elif dep.key == "energy_density" and dep.constraint_type == "range_min":
|
||||||
density_required.append((entity.name, float(dep.value)))
|
density_required.append((entity.name, float(dep.value)))
|
||||||
|
|
||||||
for req_name, req_density in density_required:
|
for req_name, req_density in density_required:
|
||||||
@@ -177,14 +147,14 @@ class ConstraintResolver:
|
|||||||
for prov_name, prov_density in density_provided:
|
for prov_name, prov_density in density_provided:
|
||||||
if prov_density < req_density * 0.25:
|
if prov_density < req_density * 0.25:
|
||||||
result.violations.append(
|
result.violations.append(
|
||||||
f"{prov_name} provides {prov_density:.0f} Wh/kg but "
|
f"{prov_name} provides {prov_density:.0f} J/kg but "
|
||||||
f"{req_name} requires {req_density:.0f} Wh/kg "
|
f"{req_name} requires {req_density:.0f} J/kg "
|
||||||
f"(energy density deficit > 4x)"
|
f"(energy density deficit > 4x)"
|
||||||
)
|
)
|
||||||
elif prov_density < req_density:
|
elif prov_density < req_density:
|
||||||
result.warnings.append(
|
result.warnings.append(
|
||||||
f"{prov_name} provides {prov_density:.0f} Wh/kg but "
|
f"{prov_name} provides {prov_density:.0f} J/kg but "
|
||||||
f"{req_name} requires {req_density:.0f} Wh/kg "
|
f"{req_name} requires {req_density:.0f} J/kg "
|
||||||
f"(under-density)"
|
f"(under-density)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -414,28 +414,27 @@ class Pipeline:
|
|||||||
def _stub_estimate(
|
def _stub_estimate(
|
||||||
self, combo: Combination, metric_names: list[str]
|
self, combo: Combination, metric_names: list[str]
|
||||||
) -> dict[str, float]:
|
) -> dict[str, float]:
|
||||||
"""Simple heuristic estimation from dependency data."""
|
"""Simple heuristic estimation from dependency data (all values in SI base units)."""
|
||||||
raw: dict[str, float] = {m: 0.0 for m in metric_names}
|
raw: dict[str, float] = {m: 0.0 for m in metric_names}
|
||||||
|
|
||||||
# Extract intrinsic properties from entities
|
# Extract intrinsic properties from entities
|
||||||
power_density = 0.0 # W/kg
|
power_density = 0.0 # W/kg
|
||||||
energy_density = 0.0 # Wh/kg
|
energy_density = 0.0 # J/kg
|
||||||
mass_kg = 100.0 # default
|
mass = 100.0 # kg, default
|
||||||
for entity in combo.entities:
|
for entity in combo.entities:
|
||||||
for dep in entity.dependencies:
|
for dep in entity.dependencies:
|
||||||
if dep.key == "power_density_w_kg" and dep.constraint_type == "provides":
|
if dep.key == "power_density" and dep.constraint_type == "provides":
|
||||||
power_density = max(power_density, float(dep.value))
|
power_density = max(power_density, float(dep.value))
|
||||||
if dep.key == "energy_density_wh_kg" and dep.constraint_type == "provides":
|
if dep.key == "energy_density" and dep.constraint_type == "provides":
|
||||||
energy_density = max(energy_density, float(dep.value))
|
energy_density = max(energy_density, float(dep.value))
|
||||||
if dep.key == "mass_kg" and dep.constraint_type == "range_min":
|
if dep.key == "mass" and dep.constraint_type == "range_min":
|
||||||
mass_kg = max(mass_kg, float(dep.value))
|
mass = max(mass, float(dep.value))
|
||||||
|
|
||||||
# Rough speed estimate: higher power density → faster
|
if "power_density" in raw:
|
||||||
if "speed" in raw:
|
raw["power_density"] = power_density
|
||||||
raw["speed"] = min(power_density * 0.5, 300000)
|
|
||||||
|
|
||||||
if "cost_efficiency" in raw:
|
if "cost_efficiency" in raw:
|
||||||
raw["cost_efficiency"] = max(0.01, 2.0 - power_density / 1000)
|
raw["cost_efficiency"] = max(1e-5, 2e-3 - power_density / 1e6)
|
||||||
|
|
||||||
if "safety" in raw:
|
if "safety" in raw:
|
||||||
raw["safety"] = 0.5
|
raw["safety"] = 0.5
|
||||||
@@ -444,19 +443,19 @@ class Pipeline:
|
|||||||
raw["availability"] = 0.5
|
raw["availability"] = 0.5
|
||||||
|
|
||||||
if "range_fuel" in raw:
|
if "range_fuel" in raw:
|
||||||
raw["range_fuel"] = min(energy_density * 10, 1e10)
|
raw["range_fuel"] = min(energy_density * 2.78, 1e13)
|
||||||
|
|
||||||
if "range_degradation" in raw:
|
if "range_degradation" in raw:
|
||||||
raw["range_degradation"] = 365
|
raw["range_degradation"] = 365 * 86400
|
||||||
|
|
||||||
if "cargo_capacity" in raw:
|
if "cargo_capacity" in raw:
|
||||||
raw["cargo_capacity"] = mass_kg * 0.5
|
raw["cargo_capacity"] = mass * 500
|
||||||
|
|
||||||
if "cargo_capacity_kg" in raw:
|
if "cargo_capacity_kg" in raw:
|
||||||
raw["cargo_capacity_kg"] = mass_kg * 0.3
|
raw["cargo_capacity_kg"] = mass * 0.3
|
||||||
|
|
||||||
if "environmental_impact" in raw:
|
if "environmental_impact" in raw:
|
||||||
raw["environmental_impact"] = max(0.0, power_density * 0.2)
|
raw["environmental_impact"] = max(0.0, power_density * 2e-7)
|
||||||
|
|
||||||
if "reliability" in raw:
|
if "reliability" in raw:
|
||||||
raw["reliability"] = 0.5
|
raw["reliability"] = 0.5
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ estimate the requested metrics using order-of-magnitude physics reasoning.
|
|||||||
- Use real-world physics to estimate each metric.
|
- Use real-world physics to estimate each metric.
|
||||||
- If the concept is implausible, still provide your best estimate.
|
- If the concept is implausible, still provide your best estimate.
|
||||||
- Return ONLY valid JSON mapping metric names to numeric values.
|
- Return ONLY valid JSON mapping metric names to numeric values.
|
||||||
- Example: {{"speed": 45.0, "cost_efficiency": 0.15, "safety": 0.7}}
|
- Example: {{"power_density": 500.0, "cost_efficiency": 0.15, "safety": 0.7}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLAUSIBILITY_REVIEW_PROMPT = """\
|
PLAUSIBILITY_REVIEW_PROMPT = """\
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ GROUND_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "50", "m²", "range_max"),
|
Dependency("physical", "footprint", "50", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "0.5", "m²", "range_min"),
|
Dependency("physical", "footprint", "0.5", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "36000", "kg", "range_max"),
|
Dependency("physical", "mass", "36000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "50", "kg", "range_min"),
|
Dependency("physical", "mass", "50", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "road_network", "true", None, "requires"),
|
Dependency("infrastructure", "road_network", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
@@ -32,30 +31,14 @@ GROUND_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "3", "m²", "range_max"),
|
Dependency("physical", "footprint", "3", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "0.3", "m²", "range_min"),
|
Dependency("physical", "footprint", "0.3", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "60", "kg", "range_max"),
|
Dependency("physical", "mass", "60", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "5", "kg", "range_min"),
|
Dependency("physical", "mass", "5", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "road_network", "true", None, "requires"),
|
Dependency("infrastructure", "road_network", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
|
||||||
name="Walking",
|
|
||||||
dimension="platform",
|
|
||||||
description="Bipedal locomotion",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
|
||||||
Dependency("physical", "footprint_m2", "0.8", "m²", "range_max"),
|
|
||||||
Dependency("physical", "footprint_m2", "0.3", "m²", "range_min"),
|
|
||||||
Dependency("physical", "mass_kg", "150", "kg", "range_max"),
|
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Entity(
|
Entity(
|
||||||
name="Rail Vehicle",
|
name="Rail Vehicle",
|
||||||
dimension="platform",
|
dimension="platform",
|
||||||
@@ -63,30 +46,14 @@ GROUND_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "200", "m²", "range_max"),
|
Dependency("physical", "footprint", "200", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "20", "m²", "range_min"),
|
Dependency("physical", "footprint", "20", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "40000", "kg", "range_max"),
|
Dependency("physical", "mass", "40000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "10000", "kg", "range_min"),
|
Dependency("physical", "mass", "10000", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "10", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "rail_network", "true", None, "requires"),
|
Dependency("infrastructure", "rail_network", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
|
||||||
name="Animal Transport",
|
|
||||||
dimension="platform",
|
|
||||||
description="Animal-powered riding or pulling — horses, dog sleds",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
|
||||||
Dependency("physical", "footprint_m2", "5", "m²", "range_max"),
|
|
||||||
Dependency("physical", "footprint_m2", "1", "m²", "range_min"),
|
|
||||||
Dependency("physical", "mass_kg", "700", "kg", "range_max"),
|
|
||||||
Dependency("physical", "mass_kg", "100", "kg", "range_min"),
|
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -100,11 +67,10 @@ WATER_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "water_surface", "true", None, "requires"),
|
Dependency("environment", "water_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "2000", "m²", "range_max"),
|
Dependency("physical", "footprint", "2000", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "2", "m²", "range_min"),
|
Dependency("physical", "footprint", "2", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "100000", "kg", "range_max"),
|
Dependency("physical", "mass", "100000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "30", "kg", "range_min"),
|
Dependency("physical", "mass", "30", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "2", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "water", None, "requires"),
|
Dependency("environment", "medium", "water", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -115,12 +81,11 @@ WATER_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "water_surface", "true", None, "requires"),
|
Dependency("environment", "water_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "200", "m²", "range_max"),
|
Dependency("physical", "footprint", "200", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "20", "m²", "range_min"),
|
Dependency("physical", "footprint", "20", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "10000", "kg", "range_min"),
|
Dependency("physical", "mass", "10000", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "water", None, "requires"),
|
Dependency("environment", "medium", "water", None, "requires"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -136,14 +101,13 @@ AIR_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "500", "m²", "range_max"),
|
Dependency("physical", "footprint", "500", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "10", "m²", "range_min"),
|
Dependency("physical", "footprint", "10", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "100000", "kg", "range_max"),
|
Dependency("physical", "mass", "100000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "500", "kg", "range_min"),
|
Dependency("physical", "mass", "500", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "100", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "runway", "true", None, "requires"),
|
Dependency("infrastructure", "runway", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "air", None, "requires"),
|
Dependency("environment", "medium", "air", None, "requires"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "1440000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -153,13 +117,12 @@ AIR_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "20", "m²", "range_max"),
|
Dependency("physical", "footprint", "20", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "0.5", "m²", "range_min"),
|
Dependency("physical", "footprint", "0.5", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "5000", "kg", "range_max"),
|
Dependency("physical", "mass", "5000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "1", "kg", "range_min"),
|
Dependency("physical", "mass", "1", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "80", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "air", None, "requires"),
|
Dependency("environment", "medium", "air", None, "requires"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -169,11 +132,10 @@ AIR_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "1000", "m²", "range_max"),
|
Dependency("physical", "footprint", "1000", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "50", "m²", "range_min"),
|
Dependency("physical", "footprint", "50", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "20000", "kg", "range_max"),
|
Dependency("physical", "mass", "20000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "200", "kg", "range_min"),
|
Dependency("physical", "mass", "200", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "air", None, "requires"),
|
Dependency("environment", "medium", "air", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -184,11 +146,10 @@ AIR_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "20", "m²", "range_max"),
|
Dependency("physical", "footprint", "20", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "5", "m²", "range_min"),
|
Dependency("physical", "footprint", "5", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "600", "kg", "range_max"),
|
Dependency("physical", "mass", "600", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "5", "kg", "range_min"),
|
Dependency("physical", "mass", "5", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "tow_or_winch", "true", None, "requires"),
|
Dependency("infrastructure", "tow_or_winch", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "air", None, "requires"),
|
Dependency("environment", "medium", "air", None, "requires"),
|
||||||
],
|
],
|
||||||
@@ -205,13 +166,12 @@ SPACE_PLATFORMS: list[Entity] = [
|
|||||||
description="Vehicle designed for space travel",
|
description="Vehicle designed for space travel",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "500", "m²", "range_max"),
|
Dependency("physical", "footprint", "500", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "10", "m²", "range_min"),
|
Dependency("physical", "footprint", "10", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "5000", "kg", "range_min"),
|
Dependency("physical", "mass", "5000", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "300", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "launch_facility", "true", None, "requires"),
|
Dependency("infrastructure", "launch_facility", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "space", None, "requires"),
|
Dependency("environment", "medium", "space", None, "requires"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "2000", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "7200000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -226,11 +186,10 @@ MULTI_PLATFORMS: list[Entity] = [
|
|||||||
description="Vehicle capable of operation on land, water, or both",
|
description="Vehicle capable of operation on land, water, or both",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "100", "m²", "range_max"),
|
Dependency("physical", "footprint", "100", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "5", "m²", "range_min"),
|
Dependency("physical", "footprint", "5", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "10000", "kg", "range_max"),
|
Dependency("physical", "mass", "10000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "1500", "kg", "range_min"),
|
Dependency("physical", "mass", "1500", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -244,10 +203,9 @@ FICTIONAL_PLATFORMS: list[Entity] = [
|
|||||||
dimension="platform",
|
dimension="platform",
|
||||||
description="Hypothetical matter transmission device",
|
description="Hypothetical matter transmission device",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "footprint_m2", "10", "m²", "range_max"),
|
Dependency("physical", "footprint", "10", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "1", "m²", "range_min"),
|
Dependency("physical", "footprint", "1", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "1000", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "teleport_network", "true", None, "requires"),
|
Dependency("infrastructure", "teleport_network", "true", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -258,11 +216,10 @@ FICTIONAL_PLATFORMS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "footprint_m2", "50", "m²", "range_max"),
|
Dependency("physical", "footprint", "50", "m²", "range_max"),
|
||||||
Dependency("physical", "footprint_m2", "5", "m²", "range_min"),
|
Dependency("physical", "footprint", "5", "m²", "range_min"),
|
||||||
Dependency("physical", "mass_kg", "20000", "kg", "range_max"),
|
Dependency("physical", "mass", "20000", "kg", "range_max"),
|
||||||
Dependency("physical", "mass_kg", "5000", "kg", "range_min"),
|
Dependency("physical", "mass", "5000", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "80", "W/kg", "range_min"),
|
|
||||||
Dependency("infrastructure", "hyperloop_tube", "true", None, "requires"),
|
Dependency("infrastructure", "hyperloop_tube", "true", None, "requires"),
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
@@ -292,9 +249,9 @@ COMBUSTION_ACTUATORS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "45", "kg", "range_min"),
|
Dependency("physical", "mass", "45", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "high_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "high_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "1000", "W/kg", "provides"),
|
Dependency("force", "power_density", "1000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -304,9 +261,9 @@ COMBUSTION_ACTUATORS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "200", "kg", "range_min"),
|
Dependency("physical", "mass", "200", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "5000", "W/kg", "provides"),
|
Dependency("force", "power_density", "5000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -316,9 +273,9 @@ COMBUSTION_ACTUATORS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "300", "kg", "range_min"),
|
Dependency("physical", "mass", "300", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "high_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "high_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "30", "W/kg", "provides"),
|
Dependency("force", "power_density", "30", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -333,9 +290,9 @@ ELECTRIC_ACTUATORS: list[Entity] = [
|
|||||||
description="Rotary motor converting electrical energy to mechanical motion",
|
description="Rotary motor converting electrical energy to mechanical motion",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "electrical", None, "requires"),
|
Dependency("energy", "energy_form", "electrical", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "5", "kg", "range_min"),
|
Dependency("physical", "mass", "5", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "2000", "W/kg", "provides"),
|
Dependency("force", "power_density", "2000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -350,9 +307,9 @@ BIOLOGICAL_ACTUATORS: list[Entity] = [
|
|||||||
description="Human-powered via pedalling, pushing, or rowing",
|
description="Human-powered via pedalling, pushing, or rowing",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "biological", None, "requires"),
|
Dependency("energy", "energy_form", "biological", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "1.5", "W/kg", "provides"),
|
Dependency("force", "power_density", "1.5", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -362,9 +319,9 @@ BIOLOGICAL_ACTUATORS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "biological", None, "requires"),
|
Dependency("energy", "energy_form", "biological", None, "requires"),
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "2", "W/kg", "provides"),
|
Dependency("force", "power_density", "2", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -381,9 +338,9 @@ RENEWABLE_ACTUATORS: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "radiation_pressure", None, "requires"),
|
Dependency("energy", "energy_form", "radiation_pressure", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
||||||
Dependency("environment", "star_proximity", "true", None, "requires"),
|
Dependency("environment", "star_proximity", "true", None, "requires"),
|
||||||
Dependency("physical", "surface_area_m2", "100", "m^2", "range_min"),
|
Dependency("physical", "surface_area", "100", "m²", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "continuous_low", None, "provides"),
|
Dependency("force", "thrust_profile", "continuous_low", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"),
|
Dependency("force", "power_density", "0.01", "W/kg", "provides"),
|
||||||
Dependency("environment", "medium", "space", None, "requires"),
|
Dependency("environment", "medium", "space", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -394,9 +351,9 @@ RENEWABLE_ACTUATORS: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "wind", None, "requires"),
|
Dependency("energy", "energy_form", "wind", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "15", "kg", "range_min"),
|
Dependency("physical", "mass", "15", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "10", "W/kg", "provides"),
|
Dependency("force", "power_density", "10", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -411,9 +368,9 @@ ROCKET_ACTUATORS: list[Entity] = [
|
|||||||
description="Thrust from expanding combustion gases through a nozzle",
|
description="Thrust from expanding combustion gases through a nozzle",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_propellant", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_propellant", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "150", "kg", "range_min"),
|
Dependency("physical", "mass", "150", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "extreme_burst", None, "provides"),
|
Dependency("force", "thrust_profile", "extreme_burst", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "10000", "W/kg", "provides"),
|
Dependency("force", "power_density", "10000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -424,9 +381,9 @@ ROCKET_ACTUATORS: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "ion_propellant", None, "requires"),
|
Dependency("energy", "energy_form", "ion_propellant", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
||||||
Dependency("environment", "medium", "space", None, "requires"),
|
Dependency("environment", "medium", "space", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "8", "kg", "range_min"),
|
Dependency("physical", "mass", "8", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "continuous_low", None, "provides"),
|
Dependency("force", "thrust_profile", "continuous_low", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "30", "W/kg", "provides"),
|
Dependency("force", "power_density", "30", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -435,9 +392,9 @@ ROCKET_ACTUATORS: list[Entity] = [
|
|||||||
description="Nuclear fission reactor heating propellant for thrust",
|
description="Nuclear fission reactor heating propellant for thrust",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "nuclear_thermal", None, "requires"),
|
Dependency("energy", "energy_form", "nuclear_thermal", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "1500", "kg", "range_min"),
|
Dependency("physical", "mass", "1500", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"),
|
Dependency("force", "power_density", "50", "W/kg", "provides"),
|
||||||
Dependency("material", "radiation_shielding", "true", None, "requires"),
|
Dependency("material", "radiation_shielding", "true", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -453,9 +410,9 @@ EXOTIC_ACTUATORS: list[Entity] = [
|
|||||||
description="Propulsion via sequential cannon blasts",
|
description="Propulsion via sequential cannon blasts",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_explosive", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_explosive", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "80", "kg", "range_min"),
|
Dependency("physical", "mass", "80", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "high_burst", None, "provides"),
|
Dependency("force", "thrust_profile", "high_burst", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "3000", "W/kg", "provides"),
|
Dependency("force", "power_density", "3000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -464,9 +421,9 @@ EXOTIC_ACTUATORS: list[Entity] = [
|
|||||||
description="Motor powered by expanding compressed air",
|
description="Motor powered by expanding compressed air",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "pneumatic", None, "requires"),
|
Dependency("energy", "energy_form", "pneumatic", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "10", "kg", "range_min"),
|
Dependency("physical", "mass", "10", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "100", "W/kg", "provides"),
|
Dependency("force", "power_density", "100", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -475,9 +432,9 @@ EXOTIC_ACTUATORS: list[Entity] = [
|
|||||||
description="Mechanical drive transferring kinetic energy from a spinning rotor",
|
description="Mechanical drive transferring kinetic energy from a spinning rotor",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "kinetic_stored", None, "requires"),
|
Dependency("energy", "energy_form", "kinetic_stored", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "20", "kg", "range_min"),
|
Dependency("physical", "mass", "20", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "high_burst", None, "provides"),
|
Dependency("force", "thrust_profile", "high_burst", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "2000", "W/kg", "provides"),
|
Dependency("force", "power_density", "2000", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -488,9 +445,9 @@ EXOTIC_ACTUATORS: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "gravitational", None, "requires"),
|
Dependency("energy", "energy_form", "gravitational", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
Dependency("force", "thrust_profile", "low_continuous", None, "provides"),
|
||||||
Dependency("force", "power_density_w_kg", "10", "W/kg", "provides"),
|
Dependency("force", "power_density", "10", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -518,8 +475,8 @@ COMBUSTIBLE_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "fuel_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "fuel_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "10", "kg", "range_min"),
|
Dependency("physical", "mass", "10", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "1300", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "4680000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -529,8 +486,8 @@ COMBUSTIBLE_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "hydrogen_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "hydrogen_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "5", "kg", "range_min"),
|
Dependency("physical", "mass", "5", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "600", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "2160000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -540,8 +497,8 @@ COMBUSTIBLE_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "cng_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "cng_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "15", "kg", "range_min"),
|
Dependency("physical", "mass", "15", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "800", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "2880000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -551,8 +508,8 @@ COMBUSTIBLE_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "coal_supply", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "coal_supply", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "100", "kg", "range_min"),
|
Dependency("physical", "mass", "100", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "1440000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -562,8 +519,8 @@ COMBUSTIBLE_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "jet_fuel", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "jet_fuel", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "50", "kg", "range_min"),
|
Dependency("physical", "mass", "50", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "1200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "4320000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -579,8 +536,8 @@ ELECTRICAL_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "9", "kg", "range_min"),
|
Dependency("physical", "mass", "9", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "300", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "1080000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -590,8 +547,8 @@ ELECTRICAL_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "15", "kg", "range_min"),
|
Dependency("physical", "mass", "15", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "10", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "36000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -602,8 +559,8 @@ ELECTRICAL_STORAGE: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
Dependency("energy", "energy_form", "electrical", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
||||||
Dependency("environment", "star_proximity", "true", None, "requires"),
|
Dependency("environment", "star_proximity", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "10", "kg", "range_min"),
|
Dependency("physical", "mass", "10", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "50", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "180000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -619,8 +576,8 @@ BIOLOGICAL_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "biological", None, "provides"),
|
Dependency("energy", "energy_form", "biological", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -637,7 +594,7 @@ AMBIENT_STORAGE: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "radiation_pressure", None, "provides"),
|
Dependency("energy", "energy_form", "radiation_pressure", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
||||||
Dependency("environment", "star_proximity", "true", None, "requires"),
|
Dependency("environment", "star_proximity", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -648,7 +605,7 @@ AMBIENT_STORAGE: list[Entity] = [
|
|||||||
Dependency("energy", "energy_form", "wind", None, "provides"),
|
Dependency("energy", "energy_form", "wind", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -664,8 +621,8 @@ PROPELLANT_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_propellant", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_propellant", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "solid_propellant", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "solid_propellant", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "50", "kg", "range_min"),
|
Dependency("physical", "mass", "50", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "500", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "1800000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -675,8 +632,8 @@ PROPELLANT_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "ion_propellant", None, "provides"),
|
Dependency("energy", "energy_form", "ion_propellant", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "xenon_propellant", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "xenon_propellant", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "2", "kg", "range_min"),
|
Dependency("physical", "mass", "2", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "3000", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "10800000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -686,8 +643,8 @@ PROPELLANT_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "nuclear_thermal", None, "provides"),
|
Dependency("energy", "energy_form", "nuclear_thermal", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "nuclear_fuel", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "nuclear_fuel", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "500", "kg", "range_min"),
|
Dependency("physical", "mass", "500", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "500000", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "1800000000", "J/kg", "provides"),
|
||||||
Dependency("material", "radiation_shielding", "true", None, "requires"),
|
Dependency("material", "radiation_shielding", "true", None, "requires"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -704,8 +661,8 @@ EXOTIC_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_explosive", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_explosive", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "ammunition", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "ammunition", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "20", "kg", "range_min"),
|
Dependency("physical", "mass", "20", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -715,8 +672,8 @@ EXOTIC_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "pneumatic", None, "provides"),
|
Dependency("energy", "energy_form", "pneumatic", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "compressed_air_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "compressed_air_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "10", "kg", "range_min"),
|
Dependency("physical", "mass", "10", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "30", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "108000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -726,8 +683,8 @@ EXOTIC_STORAGE: list[Entity] = [
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "kinetic_stored", None, "provides"),
|
Dependency("energy", "energy_form", "kinetic_stored", None, "provides"),
|
||||||
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "30", "kg", "range_min"),
|
Dependency("physical", "mass", "30", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "50", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "180000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Entity(
|
Entity(
|
||||||
@@ -739,7 +696,7 @@ EXOTIC_STORAGE: list[Entity] = [
|
|||||||
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -763,11 +720,11 @@ URBAN_COMMUTING = Domain(
|
|||||||
name="urban_commuting",
|
name="urban_commuting",
|
||||||
description="Daily travel within a city, 1-50km range",
|
description="Daily travel within a city, 1-50km range",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.25, norm_min=5, norm_max=120, unit="km/h"),
|
MetricBound("power_density", weight=0.25, norm_min=1, norm_max=2000, unit="W/kg"),
|
||||||
MetricBound("cost_efficiency", weight=0.25, norm_min=0.01, norm_max=2.0, unit="$/km", lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.25, norm_min=1e-5, norm_max=2e-3, unit="$/m", lower_is_better=True),
|
||||||
MetricBound("safety", weight=0.25, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("safety", weight=0.25, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
MetricBound("availability", weight=0.15, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("availability", weight=0.15, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
MetricBound("range_fuel", weight=0.10, norm_min=5, norm_max=500, unit="km"),
|
MetricBound("range_fuel", weight=0.10, norm_min=5000, norm_max=500000, unit="m"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -775,11 +732,11 @@ INTERPLANETARY = Domain(
|
|||||||
name="interplanetary_travel",
|
name="interplanetary_travel",
|
||||||
description="Travel between planets within a solar system",
|
description="Travel between planets within a solar system",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.30, norm_min=1000, norm_max=300000, unit="km/s"),
|
MetricBound("power_density", weight=0.30, norm_min=10, norm_max=10000, unit="W/kg"),
|
||||||
MetricBound("range_fuel", weight=0.30, norm_min=1e6, norm_max=1e10, unit="km"),
|
MetricBound("range_fuel", weight=0.30, norm_min=1e9, norm_max=1e13, unit="m"),
|
||||||
MetricBound("safety", weight=0.20, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("safety", weight=0.20, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
MetricBound("cost_efficiency", weight=0.10, norm_min=1e3, norm_max=1e9, unit="$/km", lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.10, norm_min=1.0, norm_max=1e6, unit="$/m", lower_is_better=True),
|
||||||
MetricBound("range_degradation", weight=0.10, norm_min=100, norm_max=36500, unit="days"),
|
MetricBound("range_degradation", weight=0.10, norm_min=8640000, norm_max=3.1536e9, unit="s"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -787,11 +744,11 @@ MARITIME_SHIPPING = Domain(
|
|||||||
name="maritime_shipping",
|
name="maritime_shipping",
|
||||||
description="Ocean cargo transport between ports, 100-40000km range",
|
description="Ocean cargo transport between ports, 100-40000km range",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.15, norm_min=5, norm_max=60, unit="km/h"),
|
MetricBound("power_density", weight=0.15, norm_min=1, norm_max=1000, unit="W/kg"),
|
||||||
MetricBound("cargo_capacity", weight=0.25, norm_min=1, norm_max=200000, unit="tons"),
|
MetricBound("cargo_capacity", weight=0.25, norm_min=1000, norm_max=2e8, unit="kg"),
|
||||||
MetricBound("cost_efficiency", weight=0.25, norm_min=0.001, norm_max=1.0, unit="$/ton-km", lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.25, norm_min=1e-9, norm_max=1e-6, unit="$/(kg\u00b7m)", lower_is_better=True),
|
||||||
MetricBound("safety", weight=0.20, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("safety", weight=0.20, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
MetricBound("range_fuel", weight=0.15, norm_min=100, norm_max=40000, unit="km"),
|
MetricBound("range_fuel", weight=0.15, norm_min=100000, norm_max=40000000, unit="m"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -799,11 +756,11 @@ LAST_MILE_DELIVERY = Domain(
|
|||||||
name="last_mile_delivery",
|
name="last_mile_delivery",
|
||||||
description="Short-range package delivery within neighborhoods, 0.5-15km",
|
description="Short-range package delivery within neighborhoods, 0.5-15km",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.25, norm_min=2, norm_max=60, unit="km/h"),
|
MetricBound("power_density", weight=0.25, norm_min=1, norm_max=500, unit="W/kg"),
|
||||||
MetricBound("cost_efficiency", weight=0.30, norm_min=0.01, norm_max=5.0, unit="$/km", lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.30, norm_min=1e-5, norm_max=5e-3, unit="$/m", lower_is_better=True),
|
||||||
MetricBound("cargo_capacity_kg", weight=0.20, norm_min=1, norm_max=500, unit="kg"),
|
MetricBound("cargo_capacity_kg", weight=0.20, norm_min=1, norm_max=500, unit="kg"),
|
||||||
MetricBound("safety", weight=0.15, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("safety", weight=0.15, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
MetricBound("environmental_impact", weight=0.10, norm_min=0, norm_max=500, unit="g CO2/km", lower_is_better=True),
|
MetricBound("environmental_impact", weight=0.10, norm_min=0, norm_max=5e-4, unit="kg/m", lower_is_better=True),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -811,10 +768,10 @@ CROSS_COUNTRY_FREIGHT = Domain(
|
|||||||
name="cross_country_freight",
|
name="cross_country_freight",
|
||||||
description="Long-distance overland cargo transport, 200-5000km",
|
description="Long-distance overland cargo transport, 200-5000km",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.20, norm_min=20, norm_max=200, unit="km/h"),
|
MetricBound("power_density", weight=0.20, norm_min=5, norm_max=5000, unit="W/kg"),
|
||||||
MetricBound("cargo_capacity", weight=0.25, norm_min=0.5, norm_max=100, unit="tons"),
|
MetricBound("cargo_capacity", weight=0.25, norm_min=500, norm_max=100000, unit="kg"),
|
||||||
MetricBound("cost_efficiency", weight=0.25, norm_min=0.01, norm_max=5.0, unit="$/ton-km", lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.25, norm_min=1e-8, norm_max=5e-6, unit="$/(kg\u00b7m)", lower_is_better=True),
|
||||||
MetricBound("range_fuel", weight=0.20, norm_min=100, norm_max=5000, unit="km"),
|
MetricBound("range_fuel", weight=0.20, norm_min=100000, norm_max=5000000, unit="m"),
|
||||||
MetricBound("reliability", weight=0.10, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
MetricBound("reliability", weight=0.10, norm_min=0.0, norm_max=1.0, unit="0-1"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
110
src/physcom/units.py
Normal file
110
src/physcom/units.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""Unit-aware formatting: SI base units in DB → human-friendly display."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Each rule list is sorted descending by threshold.
|
||||||
|
# format_quantity walks the list and picks the first entry where abs(value) >= threshold.
|
||||||
|
# A threshold of 0 is the fallback.
|
||||||
|
DISPLAY_RULES: dict[str, list[tuple[float, str, float]]] = {
|
||||||
|
# (threshold_in_si, display_unit, divisor_to_display)
|
||||||
|
"m": [
|
||||||
|
(9.461e15, "ly", 9.461e15),
|
||||||
|
(1.496e11, "AU", 1.496e11),
|
||||||
|
(1e3, "km", 1e3),
|
||||||
|
(1, "m", 1),
|
||||||
|
(1e-2, "cm", 1e-2),
|
||||||
|
(0, "mm", 1e-3),
|
||||||
|
],
|
||||||
|
"m/s": [
|
||||||
|
(1e3, "km/s", 1e3),
|
||||||
|
(0.3, "km/h", 1 / 3.6),
|
||||||
|
(0, "m/s", 1),
|
||||||
|
],
|
||||||
|
"m\u00b2": [
|
||||||
|
(1e6, "km\u00b2", 1e6),
|
||||||
|
(1, "m\u00b2", 1),
|
||||||
|
(0, "cm\u00b2", 1e-4),
|
||||||
|
],
|
||||||
|
"kg": [
|
||||||
|
(1e3, "t", 1e3),
|
||||||
|
(1, "kg", 1),
|
||||||
|
(0, "g", 1e-3),
|
||||||
|
],
|
||||||
|
"W/kg": [
|
||||||
|
(1e3, "kW/kg", 1e3),
|
||||||
|
(0, "W/kg", 1),
|
||||||
|
],
|
||||||
|
"J/kg": [
|
||||||
|
(3.6e6, "kWh/kg", 3.6e6),
|
||||||
|
(3600, "Wh/kg", 3600),
|
||||||
|
(1e3, "kJ/kg", 1e3),
|
||||||
|
(0, "J/kg", 1),
|
||||||
|
],
|
||||||
|
"s": [
|
||||||
|
(3.156e7, "yr", 3.156e7),
|
||||||
|
(86400, "d", 86400),
|
||||||
|
(3600, "h", 3600),
|
||||||
|
(60, "min", 60),
|
||||||
|
(0, "s", 1),
|
||||||
|
],
|
||||||
|
# Always display in human-friendly derived unit
|
||||||
|
"$/m": [
|
||||||
|
(0, "$/km", 1e-3),
|
||||||
|
],
|
||||||
|
"$/(kg\u00b7m)": [
|
||||||
|
(0, "$/t\u00b7km", 1e-6),
|
||||||
|
],
|
||||||
|
"kg/m": [
|
||||||
|
(0, "g/km", 1e-6),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_quantity(value: object, si_unit: str | None = None) -> str:
|
||||||
|
"""Format a numeric value with its SI unit into a human-readable string.
|
||||||
|
|
||||||
|
Returns e.g. "120 km/h" for (33.33, "m/s") or "1.0 ly" for (9.461e15, "m").
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return "\u2014"
|
||||||
|
|
||||||
|
# Coerce to float
|
||||||
|
if isinstance(value, str):
|
||||||
|
try:
|
||||||
|
num = float(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return value
|
||||||
|
elif isinstance(value, (int, float)):
|
||||||
|
num = float(value)
|
||||||
|
else:
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
import math
|
||||||
|
if math.isnan(num) or math.isinf(num):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
if not si_unit or si_unit == "0-1":
|
||||||
|
return _fmt_number(num)
|
||||||
|
|
||||||
|
rules = DISPLAY_RULES.get(si_unit)
|
||||||
|
if rules is None:
|
||||||
|
# No display rules — just show number + unit
|
||||||
|
return f"{_fmt_number(num)} {si_unit}"
|
||||||
|
|
||||||
|
abs_val = abs(num)
|
||||||
|
for threshold, display_unit, divisor in rules:
|
||||||
|
if abs_val >= threshold:
|
||||||
|
display_val = num / divisor
|
||||||
|
return f"{_fmt_number(display_val)} {display_unit}"
|
||||||
|
|
||||||
|
# Fallback (shouldn't reach here if rules end with threshold=0)
|
||||||
|
return f"{_fmt_number(num)} {si_unit}"
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt_number(num: float) -> str:
|
||||||
|
"""Format a number concisely: drop trailing zeros, cap at 4 significant figures."""
|
||||||
|
if num == 0:
|
||||||
|
return "0"
|
||||||
|
if num == int(num) and abs(num) < 1e6:
|
||||||
|
return str(int(num))
|
||||||
|
return f"{num:.4g}"
|
||||||
@@ -11,6 +11,7 @@ from flask import Flask, g
|
|||||||
|
|
||||||
from physcom.db.schema import init_db
|
from physcom.db.schema import init_db
|
||||||
from physcom.db.repository import Repository
|
from physcom.db.repository import Repository
|
||||||
|
from physcom.units import format_quantity
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_DB = Path("data/physcom.db")
|
DEFAULT_DB = Path("data/physcom.db")
|
||||||
@@ -101,6 +102,7 @@ def create_app() -> Flask:
|
|||||||
app.secret_key = _load_or_generate_secret_key()
|
app.secret_key = _load_or_generate_secret_key()
|
||||||
|
|
||||||
app.jinja_env.filters["si"] = _si_format
|
app.jinja_env.filters["si"] = _si_format
|
||||||
|
app.jinja_env.filters["qty"] = format_quantity
|
||||||
|
|
||||||
app.teardown_appcontext(close_db)
|
app.teardown_appcontext(close_db)
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
<td>{{ mb.metric_name }}</td>
|
<td>{{ mb.metric_name }}</td>
|
||||||
<td>{{ mb.unit or '—' }}</td>
|
<td>{{ mb.unit or '—' }}</td>
|
||||||
<td>{{ mb.weight }}</td>
|
<td>{{ mb.weight }}</td>
|
||||||
<td>{{ mb.norm_min|si }}</td>
|
<td>{{ mb.norm_min|qty(mb.unit) }}</td>
|
||||||
<td>{{ mb.norm_max|si }}</td>
|
<td>{{ mb.norm_max|qty(mb.unit) }}</td>
|
||||||
<td>{{ '↓ lower' if mb.lower_is_better else '↑ higher' }}</td>
|
<td>{{ '↓ lower' if mb.lower_is_better else '↑ higher' }}</td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<button class="btn btn-sm"
|
<button class="btn btn-sm"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
<td>{{ mb.metric_name }}</td>
|
<td>{{ mb.metric_name }}</td>
|
||||||
<td>{{ mb.unit }}</td>
|
<td>{{ mb.unit }}</td>
|
||||||
<td>{{ mb.weight }}</td>
|
<td>{{ mb.weight }}</td>
|
||||||
<td>{{ mb.norm_min|si }}</td>
|
<td>{{ mb.norm_min|qty(mb.unit) }}</td>
|
||||||
<td>{{ mb.norm_max|si }}</td>
|
<td>{{ mb.norm_max|qty(mb.unit) }}</td>
|
||||||
<td>{{ '↓ lower' if mb.lower_is_better else '↑ higher' }}</td>
|
<td>{{ '↓ lower' if mb.lower_is_better else '↑ higher' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ dep.category }}</td>
|
<td>{{ dep.category }}</td>
|
||||||
<td>{{ dep.key }}</td>
|
<td>{{ dep.key }}</td>
|
||||||
<td>{{ dep.value|si }}</td>
|
<td>{{ dep.value|qty(dep.unit) }}</td>
|
||||||
<td>{{ dep.unit or '—' }}</td>
|
<td>{{ dep.unit or '—' }}</td>
|
||||||
<td><span class="badge badge-{{ dep.constraint_type }}">{{ dep.constraint_type }}</span></td>
|
<td><span class="badge badge-{{ dep.constraint_type }}">{{ dep.constraint_type }}</span></td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
{% for dep in e.dependencies %}
|
{% for dep in e.dependencies %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ dep.key }}</td>
|
<td>{{ dep.key }}</td>
|
||||||
<td>{{ dep.value|si }}{{ ' ' + dep.unit if dep.unit else '' }}</td>
|
<td>{{ dep.value|qty(dep.unit) }}</td>
|
||||||
<td><span class="badge badge-{{ dep.constraint_type }}">{{ dep.constraint_type }}</span></td>
|
<td><span class="badge badge-{{ dep.constraint_type }}">{{ dep.constraint_type }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ s.metric_name }}</td>
|
<td>{{ s.metric_name }}</td>
|
||||||
{% set unit = s.metric_unit or '' %}
|
{% set unit = s.metric_unit or '' %}
|
||||||
<td class="score-cell">{{ s.raw_value|si if s.raw_value is not none else '—' }}{{ ' ' + unit if unit and s.raw_value is not none else '' }}</td>
|
<td class="score-cell">{{ s.raw_value|qty(unit) if s.raw_value is not none else '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{%- if mb -%}
|
{%- if mb -%}
|
||||||
{{ mb.norm_min|si }} — {{ mb.norm_max|si }}{{ ' ' + unit if unit else '' }}
|
{{ mb.norm_min|qty(unit) }} — {{ mb.norm_max|qty(unit) }}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
—
|
—
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|||||||
@@ -28,16 +28,16 @@ def seeded_repo(repo):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def walking():
|
def road_vehicle():
|
||||||
return Entity(
|
return Entity(
|
||||||
name="Walking",
|
name="Road Vehicle",
|
||||||
dimension="platform",
|
dimension="platform",
|
||||||
description="Bipedal locomotion",
|
description="Generic wheeled road vehicle",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "150", "kg", "range_max"),
|
Dependency("physical", "mass", "36000", "kg", "range_max"),
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
Dependency("physical", "mass", "50", "kg", "range_min"),
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -52,8 +52,7 @@ def bicycle():
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "ground_surface", "true", None, "requires"),
|
Dependency("environment", "ground_surface", "true", None, "requires"),
|
||||||
Dependency("environment", "gravity", "true", None, "requires"),
|
Dependency("environment", "gravity", "true", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "30", "kg", "range_max"),
|
Dependency("physical", "mass", "30", "kg", "range_max"),
|
||||||
Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "ground", None, "requires"),
|
Dependency("environment", "medium", "ground", None, "requires"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -67,8 +66,7 @@ def spaceship():
|
|||||||
description="Vehicle designed for space travel",
|
description="Vehicle designed for space travel",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "5000", "kg", "range_min"),
|
Dependency("physical", "mass", "5000", "kg", "range_min"),
|
||||||
Dependency("force", "power_density_required_w_kg", "500", "W/kg", "range_min"),
|
|
||||||
Dependency("environment", "medium", "space", None, "requires"),
|
Dependency("environment", "medium", "space", None, "requires"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -83,7 +81,7 @@ def solar_sail():
|
|||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "radiation_pressure", None, "requires"),
|
Dependency("energy", "energy_form", "radiation_pressure", None, "requires"),
|
||||||
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"),
|
||||||
Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"),
|
Dependency("force", "power_density", "0.01", "W/kg", "provides"),
|
||||||
Dependency("environment", "medium", "space", None, "requires"),
|
Dependency("environment", "medium", "space", None, "requires"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -97,7 +95,7 @@ def solar_radiation():
|
|||||||
description="Photon flux from a nearby star",
|
description="Photon flux from a nearby star",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "radiation_pressure", None, "provides"),
|
Dependency("energy", "energy_form", "radiation_pressure", None, "provides"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,8 +108,8 @@ def human_pedalling():
|
|||||||
description="Human-powered via pedalling",
|
description="Human-powered via pedalling",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "biological", None, "requires"),
|
Dependency("energy", "energy_form", "biological", None, "requires"),
|
||||||
Dependency("force", "power_density_w_kg", "1.5", "W/kg", "provides"),
|
Dependency("force", "power_density", "1.5", "W/kg", "provides"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,8 +122,8 @@ def food_calories():
|
|||||||
description="Metabolic energy from food",
|
description="Metabolic energy from food",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "biological", None, "provides"),
|
Dependency("energy", "energy_form", "biological", None, "provides"),
|
||||||
Dependency("physical", "mass_kg", "0", "kg", "range_min"),
|
Dependency("physical", "mass", "0", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,8 +136,8 @@ def nuclear_thermal_drive():
|
|||||||
description="Nuclear fission reactor for thrust",
|
description="Nuclear fission reactor for thrust",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "nuclear_thermal", None, "requires"),
|
Dependency("energy", "energy_form", "nuclear_thermal", None, "requires"),
|
||||||
Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"),
|
Dependency("force", "power_density", "50", "W/kg", "provides"),
|
||||||
Dependency("physical", "mass_kg", "1500", "kg", "range_min"),
|
Dependency("physical", "mass", "1500", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -152,8 +150,8 @@ def nuclear_fuel():
|
|||||||
description="Enriched uranium fuel rods",
|
description="Enriched uranium fuel rods",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "nuclear_thermal", None, "provides"),
|
Dependency("energy", "energy_form", "nuclear_thermal", None, "provides"),
|
||||||
Dependency("physical", "mass_kg", "500", "kg", "range_min"),
|
Dependency("physical", "mass", "500", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "500000", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "1800000000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,8 +164,8 @@ def hydrogen_engine():
|
|||||||
description="Hydrogen combustion engine",
|
description="Hydrogen combustion engine",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
||||||
Dependency("force", "power_density_w_kg", "1500", "W/kg", "provides"),
|
Dependency("force", "power_density", "1500", "W/kg", "provides"),
|
||||||
Dependency("physical", "mass_kg", "25", "kg", "range_min"),
|
Dependency("physical", "mass", "25", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,9 +178,9 @@ def ice_engine():
|
|||||||
description="Spark-ignition engine",
|
description="Spark-ignition engine",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "requires"),
|
||||||
Dependency("force", "power_density_w_kg", "1000", "W/kg", "provides"),
|
Dependency("force", "power_density", "1000", "W/kg", "provides"),
|
||||||
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
Dependency("environment", "atmosphere", "standard", None, "requires"),
|
||||||
Dependency("physical", "mass_kg", "40", "kg", "range_min"),
|
Dependency("physical", "mass", "40", "kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,8 +193,8 @@ def gasoline():
|
|||||||
description="Liquid hydrocarbon fuel",
|
description="Liquid hydrocarbon fuel",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("physical", "mass_kg", "10", "kg", "range_min"),
|
Dependency("physical", "mass", "10", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "1500", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "5400000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -209,8 +207,8 @@ def hydrogen():
|
|||||||
description="Compressed hydrogen gas",
|
description="Compressed hydrogen gas",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
Dependency("energy", "energy_form", "chemical_combustible", None, "provides"),
|
||||||
Dependency("physical", "mass_kg", "5", "kg", "range_min"),
|
Dependency("physical", "mass", "5", "kg", "range_min"),
|
||||||
Dependency("physical", "energy_density_wh_kg", "600", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "2160000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -221,10 +219,10 @@ def urban_domain():
|
|||||||
name="urban_commuting",
|
name="urban_commuting",
|
||||||
description="Daily city travel",
|
description="Daily city travel",
|
||||||
metric_bounds=[
|
metric_bounds=[
|
||||||
MetricBound("speed", weight=0.25, norm_min=5, norm_max=120),
|
MetricBound("power_density", weight=0.25, norm_min=1, norm_max=2000),
|
||||||
MetricBound("cost_efficiency", weight=0.25, norm_min=0.01, norm_max=2.0, lower_is_better=True),
|
MetricBound("cost_efficiency", weight=0.25, norm_min=1e-5, norm_max=2e-3, lower_is_better=True),
|
||||||
MetricBound("safety", weight=0.25, norm_min=0.0, norm_max=1.0),
|
MetricBound("safety", weight=0.25, norm_min=0.0, norm_max=1.0),
|
||||||
MetricBound("availability", weight=0.15, norm_min=0.0, norm_max=1.0),
|
MetricBound("availability", weight=0.15, norm_min=0.0, norm_max=1.0),
|
||||||
MetricBound("range_fuel", weight=0.10, norm_min=5, norm_max=500),
|
MetricBound("range_fuel", weight=0.10, norm_min=5000, norm_max=500000),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ def test_compatible_ground_combo(bicycle, human_pedalling, food_calories):
|
|||||||
assert result.status != "p1_fail", f"Unexpected block: {result.violations}"
|
assert result.status != "p1_fail", f"Unexpected block: {result.violations}"
|
||||||
|
|
||||||
|
|
||||||
def test_solar_sail_blocks_with_walking(walking, solar_sail, solar_radiation):
|
def test_solar_sail_blocks_with_road_vehicle(road_vehicle, solar_sail, solar_radiation):
|
||||||
"""Walking (ground) + Solar Sail (space) should be blocked by medium mutex."""
|
"""Road Vehicle (ground) + Solar Sail (space) should be blocked by medium mutex."""
|
||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
combo = Combination(entities=[walking, solar_sail, solar_radiation])
|
combo = Combination(entities=[road_vehicle, solar_sail, solar_radiation])
|
||||||
result = resolver.resolve(combo)
|
result = resolver.resolve(combo)
|
||||||
assert result.status == "p1_fail"
|
assert result.status == "p1_fail"
|
||||||
assert any("mutually exclusive" in v for v in result.violations)
|
assert any("mutually exclusive" in v for v in result.violations)
|
||||||
@@ -41,49 +41,6 @@ def test_nuclear_drive_blocks_with_bicycle(bicycle, nuclear_thermal_drive, nucle
|
|||||||
assert any("mass" in v.lower() for v in result.violations)
|
assert any("mass" in v.lower() for v in result.violations)
|
||||||
|
|
||||||
|
|
||||||
def test_power_density_mismatch_blocks():
|
|
||||||
"""A platform needing 500 W/kg and a source providing 0.01 W/kg → block."""
|
|
||||||
platform = Entity(
|
|
||||||
name="HeavyPlatform", dimension="platform",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("force", "power_density_required_w_kg", "500", "W/kg", "range_min"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
power = Entity(
|
|
||||||
name="TinyPower", dimension="actuator",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
resolver = ConstraintResolver()
|
|
||||||
combo = Combination(entities=[platform, power])
|
|
||||||
result = resolver.resolve(combo)
|
|
||||||
assert result.status == "p1_fail"
|
|
||||||
assert any("power density deficit" in v for v in result.violations)
|
|
||||||
|
|
||||||
|
|
||||||
def test_power_density_under_powered_warning():
|
|
||||||
"""Power source slightly below requirement → warning, not block."""
|
|
||||||
platform = Entity(
|
|
||||||
name="MedPlatform", dimension="platform",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("force", "power_density_required_w_kg", "100", "W/kg", "range_min"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
power = Entity(
|
|
||||||
name="WeakPower", dimension="actuator",
|
|
||||||
dependencies=[
|
|
||||||
Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
resolver = ConstraintResolver()
|
|
||||||
combo = Combination(entities=[platform, power])
|
|
||||||
result = resolver.resolve(combo)
|
|
||||||
# Under-powered but within 100x → warning, not block
|
|
||||||
assert result.status != "p1_fail"
|
|
||||||
assert any("under-powered" in w for w in result.warnings)
|
|
||||||
|
|
||||||
|
|
||||||
def test_requires_vs_excludes():
|
def test_requires_vs_excludes():
|
||||||
"""Direct requires/excludes contradiction."""
|
"""Direct requires/excludes contradiction."""
|
||||||
a = Entity(
|
a = Entity(
|
||||||
@@ -122,17 +79,17 @@ def test_hydrogen_bicycle_valid(bicycle, hydrogen_engine, hydrogen):
|
|||||||
|
|
||||||
|
|
||||||
def test_energy_density_deficit_blocks():
|
def test_energy_density_deficit_blocks():
|
||||||
"""A platform needing 2000 Wh/kg paired with a 200 Wh/kg battery → blocked."""
|
"""A platform needing 7200000 J/kg paired with a 720000 J/kg battery → blocked."""
|
||||||
platform = Entity(
|
platform = Entity(
|
||||||
name="Spaceship", dimension="platform",
|
name="Spaceship", dimension="platform",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "energy_density_wh_kg", "2000", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "7200000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
storage = Entity(
|
storage = Entity(
|
||||||
name="Battery", dimension="energy_storage",
|
name="Battery", dimension="energy_storage",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
@@ -143,17 +100,17 @@ def test_energy_density_deficit_blocks():
|
|||||||
|
|
||||||
|
|
||||||
def test_energy_density_under_density_warning():
|
def test_energy_density_under_density_warning():
|
||||||
"""A platform needing 400 Wh/kg paired with a 200 Wh/kg battery → conditional."""
|
"""A platform needing 1440000 J/kg paired with a 720000 J/kg battery → conditional."""
|
||||||
platform = Entity(
|
platform = Entity(
|
||||||
name="Airplane", dimension="platform",
|
name="Airplane", dimension="platform",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "1440000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
storage = Entity(
|
storage = Entity(
|
||||||
name="Battery", dimension="energy_storage",
|
name="Battery", dimension="energy_storage",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"),
|
Dependency("physical", "energy_density", "720000", "J/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
@@ -168,14 +125,14 @@ def test_energy_density_no_constraint_if_no_provider():
|
|||||||
platform = Entity(
|
platform = Entity(
|
||||||
name="Spaceship", dimension="platform",
|
name="Spaceship", dimension="platform",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("physical", "energy_density_wh_kg", "2000", "Wh/kg", "range_min"),
|
Dependency("physical", "energy_density", "7200000", "J/kg", "range_min"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
# Solar Sail-style: no energy_density_wh_kg declared
|
# Solar Sail-style: no energy_density declared
|
||||||
actuator = Entity(
|
actuator = Entity(
|
||||||
name="Solar Sail", dimension="actuator",
|
name="Solar Sail", dimension="actuator",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"),
|
Dependency("force", "power_density", "0.01", "W/kg", "provides"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from physcom.llm.providers.mock import MockLLMProvider
|
|||||||
|
|
||||||
|
|
||||||
def test_pass1_filters_impossible_combos(seeded_repo):
|
def test_pass1_filters_impossible_combos(seeded_repo):
|
||||||
"""Pass 1 should block known-impossible combinations (e.g., solar sail + walking)."""
|
"""Pass 1 should block known-impossible combinations (e.g., solar sail + road vehicle)."""
|
||||||
domain = seeded_repo.get_domain("urban_commuting")
|
domain = seeded_repo.get_domain("urban_commuting")
|
||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
scorer = Scorer(domain)
|
scorer = Scorer(domain)
|
||||||
@@ -44,8 +44,8 @@ def test_pass4_with_mock_llm(seeded_repo):
|
|||||||
resolver = ConstraintResolver()
|
resolver = ConstraintResolver()
|
||||||
scorer = Scorer(domain)
|
scorer = Scorer(domain)
|
||||||
mock_llm = MockLLMProvider(default_estimates={
|
mock_llm = MockLLMProvider(default_estimates={
|
||||||
"speed": 50.0, "cost_efficiency": 0.5, "safety": 0.6,
|
"power_density": 500.0, "cost_efficiency": 5e-4, "safety": 0.6,
|
||||||
"availability": 0.7, "range_fuel": 200.0,
|
"availability": 0.7, "range_fuel": 200000.0,
|
||||||
})
|
})
|
||||||
pipeline = Pipeline(seeded_repo, resolver, scorer, llm=mock_llm)
|
pipeline = Pipeline(seeded_repo, resolver, scorer, llm=mock_llm)
|
||||||
|
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ def test_p4_fail_implausible(seeded_repo):
|
|||||||
# Low estimates → normalized scores avg <= 0.5 → MockLLMProvider returns (text, False)
|
# Low estimates → normalized scores avg <= 0.5 → MockLLMProvider returns (text, False)
|
||||||
# Use threshold=0.0 so no combo gets p3_fail and all reach pass 4
|
# Use threshold=0.0 so no combo gets p3_fail and all reach pass 4
|
||||||
mock_llm = MockLLMProvider(default_estimates={
|
mock_llm = MockLLMProvider(default_estimates={
|
||||||
"speed": 0.1, "cost_efficiency": 0.1, "safety": 0.1,
|
"power_density": 0.1, "cost_efficiency": 0.1, "safety": 0.1,
|
||||||
"availability": 0.1, "range_fuel": 0.1,
|
"availability": 0.1, "range_fuel": 0.1,
|
||||||
})
|
})
|
||||||
pipeline = Pipeline(repo, resolver, scorer, llm=mock_llm)
|
pipeline = Pipeline(repo, resolver, scorer, llm=mock_llm)
|
||||||
@@ -377,8 +377,8 @@ def test_p4_pass_plausible(seeded_repo):
|
|||||||
scorer = Scorer(domain)
|
scorer = Scorer(domain)
|
||||||
# High estimates → avg > 0.5 → MockLLMProvider returns (text, True)
|
# High estimates → avg > 0.5 → MockLLMProvider returns (text, True)
|
||||||
mock_llm = MockLLMProvider(default_estimates={
|
mock_llm = MockLLMProvider(default_estimates={
|
||||||
"speed": 50.0, "cost_efficiency": 0.5, "safety": 0.6,
|
"power_density": 500.0, "cost_efficiency": 5e-4, "safety": 0.6,
|
||||||
"availability": 0.7, "range_fuel": 200.0,
|
"availability": 0.7, "range_fuel": 200000.0,
|
||||||
})
|
})
|
||||||
pipeline = Pipeline(repo, resolver, scorer, llm=mock_llm)
|
pipeline = Pipeline(repo, resolver, scorer, llm=mock_llm)
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ class TestScorer:
|
|||||||
Entity(name="ICE", dimension="actuator"),
|
Entity(name="ICE", dimension="actuator"),
|
||||||
])
|
])
|
||||||
combo.id = 1
|
combo.id = 1
|
||||||
raw = {"speed": 60.0, "cost_efficiency": 0.5, "safety": 0.7,
|
raw = {"power_density": 500.0, "cost_efficiency": 5e-4, "safety": 0.7,
|
||||||
"availability": 0.8, "range_fuel": 400}
|
"availability": 0.8, "range_fuel": 400000}
|
||||||
result = scorer.score_combination(combo, raw)
|
result = scorer.score_combination(combo, raw)
|
||||||
assert 0.0 < result.composite_score <= 1.0
|
assert 0.0 < result.composite_score <= 1.0
|
||||||
assert len(result.scores) == 5
|
assert len(result.scores) == 5
|
||||||
@@ -85,8 +85,8 @@ class TestScorer:
|
|||||||
scorer = Scorer(urban_domain)
|
scorer = Scorer(urban_domain)
|
||||||
combo = Combination(entities=[])
|
combo = Combination(entities=[])
|
||||||
combo.id = 1
|
combo.id = 1
|
||||||
raw = {"speed": 60.0, "cost_efficiency": 0.5, "safety": 0.0,
|
raw = {"power_density": 500.0, "cost_efficiency": 5e-4, "safety": 0.0,
|
||||||
"availability": 0.8, "range_fuel": 400}
|
"availability": 0.8, "range_fuel": 400000}
|
||||||
result = scorer.score_combination(combo, raw)
|
result = scorer.score_combination(combo, raw)
|
||||||
assert result.composite_score == 0.0
|
assert result.composite_score == 0.0
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ class TestScorer:
|
|||||||
scorer = Scorer(urban_domain)
|
scorer = Scorer(urban_domain)
|
||||||
combo = Combination(entities=[])
|
combo = Combination(entities=[])
|
||||||
combo.id = 1
|
combo.id = 1
|
||||||
# cost_efficiency: norm_min=0.01, norm_max=2.0, lower_is_better=True
|
# cost_efficiency: norm_min=1e-5, norm_max=2e-3, lower_is_better=True
|
||||||
# A low cost (0.02) should get a HIGH normalized score (near 1.0)
|
# A low cost (2e-5) should get a HIGH normalized score (near 1.0)
|
||||||
# A high cost (1.9) should get a LOW normalized score (near 0.0)
|
# A high cost (1.9e-3) should get a LOW normalized score (near 0.0)
|
||||||
raw_cheap = {"speed": 60.0, "cost_efficiency": 0.02, "safety": 0.7,
|
raw_cheap = {"power_density": 500.0, "cost_efficiency": 2e-5, "safety": 0.7,
|
||||||
"availability": 0.8, "range_fuel": 400}
|
"availability": 0.8, "range_fuel": 400000}
|
||||||
raw_expensive = {"speed": 60.0, "cost_efficiency": 1.9, "safety": 0.7,
|
raw_expensive = {"power_density": 500.0, "cost_efficiency": 1.9e-3, "safety": 0.7,
|
||||||
"availability": 0.8, "range_fuel": 400}
|
"availability": 0.8, "range_fuel": 400000}
|
||||||
result_cheap = scorer.score_combination(combo, raw_cheap)
|
result_cheap = scorer.score_combination(combo, raw_cheap)
|
||||||
result_expensive = scorer.score_combination(combo, raw_expensive)
|
result_expensive = scorer.score_combination(combo, raw_expensive)
|
||||||
|
|
||||||
|
|||||||
102
tests/test_units.py
Normal file
102
tests/test_units.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"""Tests for the unit-aware formatter."""
|
||||||
|
|
||||||
|
from physcom.units import format_quantity
|
||||||
|
|
||||||
|
|
||||||
|
class TestFormatQuantity:
|
||||||
|
def test_speed_km_h(self):
|
||||||
|
"""33.33 m/s should display as ~120 km/h."""
|
||||||
|
result = format_quantity(33.33, "m/s")
|
||||||
|
assert "km/h" in result
|
||||||
|
assert "120" in result
|
||||||
|
|
||||||
|
def test_speed_km_s(self):
|
||||||
|
"""10000 m/s should display as 10 km/s."""
|
||||||
|
result = format_quantity(10000, "m/s")
|
||||||
|
assert "km/s" in result
|
||||||
|
|
||||||
|
def test_distance_km(self):
|
||||||
|
"""5000 m should display as 5 km."""
|
||||||
|
result = format_quantity(5000, "m")
|
||||||
|
assert "km" in result
|
||||||
|
assert "5" in result
|
||||||
|
|
||||||
|
def test_distance_ly(self):
|
||||||
|
"""9.461e15 m should display as ~1 ly."""
|
||||||
|
result = format_quantity(9.461e15, "m")
|
||||||
|
assert "ly" in result
|
||||||
|
|
||||||
|
def test_distance_au(self):
|
||||||
|
"""1.496e11 m should display as ~1 AU."""
|
||||||
|
result = format_quantity(1.496e11, "m")
|
||||||
|
assert "AU" in result
|
||||||
|
|
||||||
|
def test_mass_tons(self):
|
||||||
|
"""5000 kg should display as 5 t."""
|
||||||
|
result = format_quantity(5000, "kg")
|
||||||
|
assert "t" in result
|
||||||
|
|
||||||
|
def test_mass_kg(self):
|
||||||
|
"""50 kg should display as 50 kg."""
|
||||||
|
result = format_quantity(50, "kg")
|
||||||
|
assert "kg" in result
|
||||||
|
|
||||||
|
def test_energy_density_wh_kg(self):
|
||||||
|
"""720000 J/kg should display as 200 Wh/kg."""
|
||||||
|
result = format_quantity(720000, "J/kg")
|
||||||
|
assert "Wh/kg" in result
|
||||||
|
assert "200" in result
|
||||||
|
|
||||||
|
def test_time_days(self):
|
||||||
|
"""86400 s should display as 1 d."""
|
||||||
|
result = format_quantity(86400, "s")
|
||||||
|
assert "d" in result
|
||||||
|
|
||||||
|
def test_time_years(self):
|
||||||
|
"""3.156e7 s should display as ~1 yr."""
|
||||||
|
result = format_quantity(3.156e7, "s")
|
||||||
|
assert "yr" in result
|
||||||
|
|
||||||
|
def test_cost_per_km(self):
|
||||||
|
"""$/m always displays as $/km."""
|
||||||
|
result = format_quantity(0.001, "$/m")
|
||||||
|
assert "$/km" in result
|
||||||
|
|
||||||
|
def test_cost_per_ton_km(self):
|
||||||
|
"""$/(kg·m) always displays as $/t·km."""
|
||||||
|
result = format_quantity(1e-6, "$/(kg\u00b7m)")
|
||||||
|
assert "$/t\u00b7km" in result
|
||||||
|
|
||||||
|
def test_emissions_g_per_km(self):
|
||||||
|
"""kg/m always displays as g/km."""
|
||||||
|
result = format_quantity(1e-4, "kg/m")
|
||||||
|
assert "g/km" in result
|
||||||
|
|
||||||
|
def test_none_returns_dash(self):
|
||||||
|
assert format_quantity(None, "m/s") == "\u2014"
|
||||||
|
|
||||||
|
def test_dimensionless(self):
|
||||||
|
"""0-1 unit should just return the number."""
|
||||||
|
result = format_quantity(0.75, "0-1")
|
||||||
|
assert result == "0.75"
|
||||||
|
|
||||||
|
def test_string_input(self):
|
||||||
|
"""String values should be converted to float."""
|
||||||
|
result = format_quantity("5000", "m")
|
||||||
|
assert "km" in result
|
||||||
|
|
||||||
|
def test_non_numeric_string(self):
|
||||||
|
"""Non-numeric string values returned as-is."""
|
||||||
|
assert format_quantity("true", "m") == "true"
|
||||||
|
|
||||||
|
def test_zero(self):
|
||||||
|
result = format_quantity(0, "m/s")
|
||||||
|
assert "m/s" in result
|
||||||
|
|
||||||
|
def test_area_m2(self):
|
||||||
|
result = format_quantity(50, "m\u00b2")
|
||||||
|
assert "m\u00b2" in result
|
||||||
|
|
||||||
|
def test_power_density(self):
|
||||||
|
result = format_quantity(5000, "W/kg")
|
||||||
|
assert "kW/kg" in result
|
||||||
Reference in New Issue
Block a user