feat: integrate AMESim media models and editor UI

This commit is contained in:
ljz committed 2026-07-31 23:36:58 +08:00
1 parent 3e4f466114
commit e7177ab03e
181 files changed
+7042 -25270

No files matched your search

@@ -3,7 +3,7 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.systems import test_mql
from app.simulation.examples.test_mql import system as test_mql
MATRIX_PATH = Path(__file__).resolve().parents[1] / "docs" / "amesim-component-migration-matrix.md"
+472
View File
@@ -0,0 +1,472 @@
from __future__ import annotations
import unittest
from app.main import (
ReactFlowProjectPayload,
build_reactflow_system_xml,
compile_reactflow_network,
)
from app.system_xml import validate_system_xml_document
from app.simulation.components.amesim.gases import (
AMESIM_BUILTIN_AIR_GAS_INDEX,
AMESIM_DEFAULT_GAS_INDEX,
AmesimGasDefinition,
AmesimGasRegistry,
default_amesim_gas_registry,
normalize_amesim_defined_gas_index,
normalize_amesim_gas_index,
)
from app.simulation.components.amesim.media import AmesimIdealAirMedium
from app.simulation.core.medium import IdealGasMedium
from app.simulation.registry import COMPONENT_MODEL_REGISTRY
from tests.test_generic_system_xml_simulation import component_node, physical_edge
from tests.test_system_xml_protocol import physical_port
GAS_INDEXED_MODEL_TYPES = (
"amesim_pnch023",
"amesim_pnch012",
"amesim_pnor001",
"amesim_pnvo001_fixed",
"amesim_pnvo001",
"amesim_pnl00r",
"amesim_pnl0001",
"amesim_pnl0002",
"amesim_pnl0003",
)
def pneumatic_pipe_node(
component_id: str,
*,
gi: float,
) -> dict[str, object]:
return component_node(
component_id,
"amesim_pnl00r",
[
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.01, "le": 1.0, "rr": 1.0e-5, "gi": gi},
)
def pneumatic_chamber_node(
component_id: str,
*,
gi: float | None = None,
volume: float = 0.01,
pressure: float = 200000.0,
temperature: float = 300.0,
) -> dict[str, object]:
parameters = {
"cvol": volume,
"kth": 0.0,
"sth": 0.1,
"extemp": 293.15,
"p0": pressure,
"T0": temperature,
}
if gi is not None:
parameters["gi"] = gi
return component_node(
component_id,
"amesim_pnch023",
[
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
parameters,
)
def ideal_air_medium_node(
component_id: str,
*,
gi: float,
property_model: float | None = None,
) -> dict[str, object]:
parameters = {"gi": gi}
if property_model is not None:
parameters["property_model"] = property_model
return component_node(
component_id,
"amesim_ideal_air_medium",
[],
parameters,
)
def two_slot_registry() -> tuple[AmesimGasRegistry, IdealGasMedium, IdealGasMedium]:
future_helium = IdealGasMedium(
name="FutureHeliumStub",
R_gas=2077.26439404998,
cp_ref=5193.0,
viscosity_ref=1.96e-5,
sutherland_constant=79.4,
)
registry = AmesimGasRegistry(
(
AmesimGasDefinition(
gi=2,
label="Future helium",
medium=future_helium,
fluid_type=12,
eos_type=6,
),
)
)
air = registry.resolve(AMESIM_BUILTIN_AIR_GAS_INDEX)
return registry, air, future_helium
class AmesimGasRegistryTests(unittest.TestCase):
def test_all_gas_sensitive_models_expose_the_same_gi_contract(self) -> None:
medium = IdealGasMedium()
for model_type in GAS_INDEXED_MODEL_TYPES:
with self.subTest(model_type=model_type):
spec = COMPONENT_MODEL_REGISTRY[model_type]
parameter = spec.parameter_by_name["gi"]
self.assertIs(spec.parameters[0], parameter)
self.assertEqual(parameter.default, 0.0)
self.assertEqual(parameter.minimum, 0.0)
self.assertEqual(parameter.maximum, 99.0)
self.assertEqual(parameter.editor, "amesimGasReference")
component = spec.create(f"{model_type}_1", medium, {})
self.assertEqual(component.gi, AMESIM_DEFAULT_GAS_INDEX)
with self.assertRaisesRegex(ValueError, "gi must be an integer"):
spec.create(f"{model_type}_fractional", medium, {"gi": 1.5})
def test_default_slot_resolves_to_air(self) -> None:
registry = default_amesim_gas_registry()
medium = registry.resolve(AMESIM_DEFAULT_GAS_INDEX)
self.assertIsInstance(medium, AmesimIdealAirMedium)
self.assertEqual(medium.name, "AMESimAirIdealGas")
self.assertEqual(medium.SUBSTANCE_ID, "air")
self.assertEqual(medium.PROPERTY_METHOD_ID, "ideal_gas")
self.assertEqual(medium.R_gas, 287.0)
self.assertEqual(medium.cp_ref, 1005.0)
self.assertEqual(medium.cv, 718.0)
def test_gi_normalization_rejects_fractional_and_out_of_range_values(self) -> None:
self.assertEqual(normalize_amesim_gas_index(0.0), 0)
self.assertEqual(normalize_amesim_gas_index(1.0), 1)
with self.assertRaisesRegex(ValueError, "integer"):
normalize_amesim_gas_index(1.5)
with self.assertRaisesRegex(ValueError, "between 0 and 99"):
normalize_amesim_gas_index(-1)
with self.assertRaisesRegex(ValueError, "between 0 and 99"):
normalize_amesim_gas_index(100)
with self.assertRaisesRegex(ValueError, "reserved"):
normalize_amesim_defined_gas_index(0)
self.assertEqual(normalize_amesim_defined_gas_index(1), 1)
def test_unknown_slot_is_not_silently_replaced_with_air(self) -> None:
registry = default_amesim_gas_registry()
with self.assertRaisesRegex(ValueError, "gi=2.*not defined"):
registry.resolve(2, component_name="pipe_1")
project = ReactFlowProjectPayload(
name="undefined-gas",
nodes=[pneumatic_pipe_node("pipe_1", gi=2.0)],
)
with self.assertRaisesRegex(ValueError, "gi=2.*pipe_1.*not defined"):
compile_reactflow_network(project)
def test_custom_registry_can_define_future_helium_slot(self) -> None:
registry, air, future_helium = two_slot_registry()
self.assertIs(registry.resolve(0), air)
self.assertIs(registry.resolve(2), future_helium)
self.assertEqual(registry.definitions[2].fluid_type, 12)
self.assertEqual(registry.definitions[2].eos_type, 6)
def test_builtin_air_slot_cannot_be_overridden(self) -> None:
registry = default_amesim_gas_registry()
with self.assertRaisesRegex(ValueError, "gi=0.*reserved"):
registry.register(
AmesimGasDefinition(
gi=0,
label="Invalid override",
medium=IdealGasMedium(name="NotBuiltinAir"),
)
)
self.assertEqual(registry.resolve(0).name, "AMESimAirIdealGas")
def test_air_medium_definition_is_a_zero_port_compile_time_component(self) -> None:
spec = COMPONENT_MODEL_REGISTRY["amesim_ideal_air_medium"]
self.assertEqual(spec.display.category_id, "media")
self.assertEqual(spec.display.role, "amesimGasMediumDefinition")
self.assertEqual(spec.display.label, "空气介质定义")
self.assertEqual(spec.ports, ())
self.assertEqual(spec.parameters[0].minimum, 1.0)
property_model = spec.parameter_by_name["property_model"]
self.assertIs(spec.parameters[1], property_model)
self.assertEqual(property_model.default, 0.0)
self.assertEqual(property_model.editor, "amesimGasPropertyModel")
self.assertEqual(
[(option.value, option.label) for option in property_model.options],
[(0, "理想气体")],
)
project = ReactFlowProjectPayload(
name="project-air-slot",
nodes=[
pneumatic_pipe_node("pipe_1", gi=1.0),
ideal_air_medium_node("air_properties", gi=1.0),
],
)
network = compile_reactflow_network(project)
self.assertNotIn("air_properties", network.components)
self.assertEqual(
network.components["pipe_1"].medium.name,
"AMESimAirIdealGas",
)
def test_air_definition_dispatches_the_selected_property_model(self) -> None:
spec = COMPONENT_MODEL_REGISTRY["amesim_ideal_air_medium"]
definition = spec.create(
"air_properties",
IdealGasMedium(),
{"gi": 3.0, "property_model": 0.0},
)
gas_definition = definition.gas_definition()
self.assertEqual(
definition.parameter_values,
{"gi": 3.0, "property_model": 0.0},
)
self.assertEqual(gas_definition.label, "空气(理想气体)")
self.assertEqual(gas_definition.fluid_type, 2)
self.assertEqual(gas_definition.eos_type, 1)
self.assertIsInstance(gas_definition.medium, AmesimIdealAirMedium)
self.assertEqual(
gas_definition.medium.PROPERTY_METHOD_ID,
"ideal_gas",
)
with self.assertRaisesRegex(ValueError, "property_model.*at most 0"):
spec.create(
"unsupported_air_properties",
IdealGasMedium(),
{"gi": 4.0, "property_model": 1.0},
)
def test_project_medium_definition_may_appear_after_its_references(self) -> None:
project = ReactFlowProjectPayload(
name="two-phase-medium-resolution",
nodes=[
pneumatic_chamber_node("chamber_1", gi=7.0),
ideal_air_medium_node("air_properties", gi=7.0),
],
)
network = compile_reactflow_network(project)
self.assertEqual(
network.components["chamber_1"].medium.name,
"AMESimAirIdealGas",
)
def test_medium_definition_and_reference_survive_system_xml_round_trip(
self,
) -> None:
original = ReactFlowProjectPayload(
name="medium-xml-round-trip",
nodes=[
ideal_air_medium_node(
"air_properties",
gi=4.0,
property_model=0.0,
),
pneumatic_pipe_node("pipe_1", gi=4.0),
],
)
xml_bytes = build_reactflow_system_xml(original)
report = validate_system_xml_document(xml_bytes)
self.assertTrue(report.valid, report.issues)
self.assertIsNotNone(report.document)
assert report.document is not None
self.assertEqual(report.document.medium_reference_version, "1")
components = {
component.id: component for component in report.document.components
}
self.assertEqual(
components["air_properties"].model_type,
"amesim_ideal_air_medium",
)
self.assertEqual(
{item.name: item.value for item in components["air_properties"].parameters},
{"gi": 4.0, "property_model": 0.0},
)
self.assertEqual(
{item.name: item.value for item in components["pipe_1"].parameters}[
"gi"
],
4.0,
)
parsed_project = ReactFlowProjectPayload(
**report.document.as_project_data()
)
network = compile_reactflow_network(parsed_project)
self.assertNotIn("air_properties", network.components)
self.assertIsInstance(
network.components["pipe_1"].medium,
AmesimIdealAirMedium,
)
def test_duplicate_project_medium_indices_are_rejected(self) -> None:
project = ReactFlowProjectPayload(
name="duplicate-medium-index",
nodes=[
ideal_air_medium_node("air_properties_1", gi=1.0),
ideal_air_medium_node("air_properties_2", gi=1.0),
],
)
with self.assertRaisesRegex(
ValueError,
"air_properties_2.*gi=1.*already defined",
):
compile_reactflow_network(project)
def test_medium_definition_cannot_claim_builtin_slot_zero(self) -> None:
project = ReactFlowProjectPayload(
name="reserved-medium-index",
nodes=[ideal_air_medium_node("air_properties", gi=0.0)],
)
with self.assertRaisesRegex(ValueError, "gi.*at least 1"):
compile_reactflow_network(project)
def test_default_compile_uses_air_before_state_initialization(self) -> None:
project = ReactFlowProjectPayload(
name="default-air",
nodes=[pneumatic_chamber_node("chamber_1")],
)
network = compile_reactflow_network(project)
chamber = network.components["chamber_1"]
self.assertEqual(chamber.medium.name, "AMESimAirIdealGas")
self.assertAlmostEqual(
chamber.state.m,
chamber.medium.density(200000.0, 300.0) * 0.01,
)
def test_gi_is_resolved_before_dynamic_state_initialization(self) -> None:
registry, _, future_helium = two_slot_registry()
project = ReactFlowProjectPayload(
name="selected-medium-initialization",
nodes=[pneumatic_chamber_node("chamber_1", gi=2.0)],
)
network = compile_reactflow_network(
project,
amesim_gas_registry=registry,
)
chamber = network.components["chamber_1"]
self.assertIs(chamber.medium, future_helium)
self.assertAlmostEqual(
chamber.state.m,
future_helium.density(200000.0, 300.0) * 0.01,
)
self.assertNotAlmostEqual(
chamber.state.m,
IdealGasMedium().density(200000.0, 300.0) * 0.01,
)
def test_selected_medium_is_injected_into_the_whole_pneumatic_circuit(
self,
) -> None:
registry, _, future_helium = two_slot_registry()
project = ReactFlowProjectPayload(
name="future-helium-circuit",
nodes=[
pneumatic_pipe_node("pnl_1", gi=2.0),
component_node(
"tank_1",
"tank",
[physical_port("port_a", "inlet", "left")],
{"volume": 0.1, "p0": 100000.0, "T0": 300.0},
),
],
edges=[
physical_edge("edge-1", "pnl_1", "port_2", "tank_1", "port_a"),
],
)
network = compile_reactflow_network(
project,
amesim_gas_registry=registry,
)
self.assertIs(network.components["pnl_1"].medium, future_helium)
self.assertIs(network.components["tank_1"].medium, future_helium)
def test_connected_circuit_rejects_conflicting_gas_indices(self) -> None:
registry, _, _ = two_slot_registry()
project = ReactFlowProjectPayload(
name="conflicting-gases",
nodes=[
pneumatic_pipe_node("air_pipe", gi=0.0),
pneumatic_pipe_node("helium_pipe", gi=2.0),
],
edges=[
physical_edge(
"edge-1",
"air_pipe",
"port_2",
"helium_pipe",
"port_1",
),
],
)
with self.assertRaisesRegex(
ValueError,
"conflicting AMESim gas definitions",
):
compile_reactflow_network(
project,
amesim_gas_registry=registry,
)
def test_disconnected_circuits_may_use_different_gas_indices(self) -> None:
registry, air, future_helium = two_slot_registry()
project = ReactFlowProjectPayload(
name="separate-gases",
nodes=[
pneumatic_pipe_node("air_pipe", gi=0.0),
pneumatic_pipe_node("helium_pipe", gi=2.0),
],
)
network = compile_reactflow_network(
project,
amesim_gas_registry=registry,
)
self.assertIs(network.components["air_pipe"].medium, air)
self.assertIs(network.components["helium_pipe"].medium, future_helium)
if __name__ == "__main__":
unittest.main()
+154
View File
@@ -0,0 +1,154 @@
from __future__ import annotations
import unittest
from app.main import ReactFlowProjectPayload, compile_reactflow_network
from app.simulation.components.amesim.library import LIBRARY
from app.simulation.components.amesim.media.mediums import (
AMESIM_AIR_PROPERTY_MODELS,
AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL,
AMESIM_HELIUM_PROPERTY_MODELS,
AmesimHeliumPengRobinsonMedium,
)
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.peng_robinson import HELIUM_PR
from app.simulation.registry import COMPONENT_MODEL_REGISTRY
from tests.test_generic_system_xml_simulation import component_node
from tests.test_system_xml_protocol import physical_port
class AmesimHeliumPengRobinsonMediumTests(unittest.TestCase):
def test_uses_shared_peng_robinson_eos_and_committed_caloric_constants(
self,
) -> None:
medium = AmesimHeliumPengRobinsonMedium()
pressure = 15.3e6
temperature = 293.15
density = medium.density(pressure, temperature)
volume = 0.02
self.assertIs(medium.fluid, HELIUM_PR)
self.assertEqual(medium.SUBSTANCE_ID, "helium")
self.assertEqual(medium.PROPERTY_METHOD_ID, "peng_robinson")
self.assertAlmostEqual(medium.R_gas, HELIUM_PR.specific_gas_constant)
self.assertEqual(medium.cp_ref, 5193.0)
self.assertEqual(medium.cv, 3116.0)
self.assertEqual(medium.cp_at_temperature(400.0), 5193.0)
self.assertEqual(medium.cv_at_temperature(400.0), 3116.0)
self.assertAlmostEqual(medium.gamma, 5193.0 / 3116.0)
self.assertAlmostEqual(density, HELIUM_PR.density(pressure, temperature))
self.assertAlmostEqual(
medium.pressure(density * volume, temperature, volume),
pressure,
delta=pressure * 1.0e-12,
)
def test_constant_caloric_model_and_sutherland_viscosity_complete_contract(
self,
) -> None:
medium = AmesimHeliumPengRobinsonMedium()
temperature = 340.0
internal_energy = medium.specific_internal_energy(temperature)
enthalpy = medium.specific_enthalpy(temperature)
self.assertEqual(internal_energy, 3116.0 * temperature)
self.assertEqual(enthalpy, 5193.0 * temperature)
self.assertEqual(
medium.temperature_from_internal_energy(internal_energy),
temperature,
)
self.assertEqual(
medium.temperature_from_enthalpy(enthalpy),
temperature,
)
self.assertAlmostEqual(medium.dynamic_viscosity(293.15), 1.96e-5)
self.assertEqual(medium.sutherland_constant, 79.4)
def test_definition_maps_local_selector_to_amesim_fluid_and_eos_codes(
self,
) -> None:
self.assertEqual(LIBRARY.version, "0.2.0")
self.assertEqual(AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL, 0)
self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].value, 0)
self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].eos_type, 6)
self.assertEqual(AMESIM_AIR_PROPERTY_MODELS[0].eos_type, 1)
spec = COMPONENT_MODEL_REGISTRY["amesim_helium_medium"]
property_model = spec.parameter_by_name["property_model"]
self.assertEqual(spec.display.category_id, "media")
self.assertEqual(spec.display.role, "amesimGasMediumDefinition")
self.assertEqual(spec.display.label, "氦气介质定义")
self.assertEqual(spec.model_version, "0.1.0")
self.assertEqual(spec.ports, ())
self.assertEqual(property_model.default, 0.0)
self.assertEqual(property_model.editor, "amesimGasPropertyModel")
self.assertEqual(
[(option.value, option.label) for option in property_model.options],
[(0, "Peng–Robinson")],
)
component = spec.create(
"helium_properties",
IdealGasMedium(),
{"gi": 2.0, "property_model": 0.0},
)
definition = component.gas_definition()
self.assertEqual(component.parameter_values, {"gi": 2.0, "property_model": 0.0})
self.assertEqual(definition.label, "氦气(Peng–Robinson)")
self.assertEqual(definition.fluid_type, 12)
self.assertEqual(definition.eos_type, 6)
self.assertIsInstance(definition.medium, AmesimHeliumPengRobinsonMedium)
def test_project_compile_injects_helium_before_dynamic_state_initialization(
self,
) -> None:
pressure = 13_839_965.0
temperature = 287.7322
volume = 0.01
project = ReactFlowProjectPayload(
name="helium-peng-robinson-compile",
nodes=[
component_node(
"helium_properties",
"amesim_helium_medium",
[],
{"gi": 2.0, "property_model": 0.0},
),
component_node(
"chamber_1",
"amesim_pnch023",
[
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{
"gi": 2.0,
"cvol": volume,
"kth": 0.0,
"sth": 0.1,
"extemp": 293.15,
"p0": pressure,
"T0": temperature,
},
),
],
)
network = compile_reactflow_network(project)
chamber = network.components["chamber_1"]
self.assertNotIn("helium_properties", network.components)
self.assertIsInstance(chamber.medium, AmesimHeliumPengRobinsonMedium)
self.assertAlmostEqual(
chamber.state.m,
HELIUM_PR.density(pressure, temperature) * volume,
delta=1.0e-12,
)
if __name__ == "__main__":
unittest.main()
+2 -2
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.components.amesim_mechanical import (
from app.simulation.examples.test_mql.primitives.mechanical import (
AmesimElasticEndstop,
AmesimMassFrictionEndstops,
AmesimPistonGeometry,
@@ -11,7 +11,7 @@ from PythonModels.components.amesim_mechanical import (
m_to_mm,
mm_to_m,
)
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
REPO_ROOT = Path(__file__).resolve().parents[1]
+1 -1
View File
@@ -40,7 +40,7 @@ def amesim_pnch012_project() -> ReactFlowProjectPayload:
"kth": 0.0,
"sth": 0.7,
"extemp": 300.0,
"gi": 1.0,
"gi": 0.0,
"p0": 200000.0,
"T0": 300.0,
"vol1": 0.001,
+1 -1
View File
@@ -25,7 +25,7 @@ class AmesimPnch023ComponentTests(unittest.TestCase):
{"cvol", "kth", "sth", "extemp", "gi", "p0", "T0"},
)
self.assertEqual(chamber.parameter_values["cvol"], 0.057)
self.assertEqual(chamber.gi, 1)
self.assertEqual(chamber.gi, 0)
def test_rejects_fractional_gas_index(self) -> None:
with self.assertRaisesRegex(ValueError, "gi must be an integer"):
+6 -2
View File
@@ -44,7 +44,7 @@ def amesim_pnch023_project() -> ReactFlowProjectPayload:
"kth": 0.0,
"sth": 0.1,
"extemp": 293.15,
"gi": 1.0,
"gi": 0.0,
"p0": 100000.0,
"T0": 293.15,
},
@@ -56,7 +56,7 @@ def amesim_pnch023_project() -> ReactFlowProjectPayload:
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.02, "le": 1.0, "rr": 1.0e-5, "gi": 1.0},
{"diam": 0.02, "le": 1.0, "rr": 1.0e-5, "gi": 0.0},
),
component_node(
"tank_1",
@@ -91,6 +91,10 @@ class AmesimPnch023XmlTests(unittest.TestCase):
def test_pnch023_system_xml_validates_and_simulates(self) -> None:
xml = build_reactflow_system_xml(amesim_pnch023_project())
xml_text = xml.decode("utf-8")
self.assertNotIn("description", xml_text)
self.assertIn('<Parameter name="cvol" value="0.057"', xml_text)
self.assertIn('<Parameter name="gi" value="0.0"', xml_text)
report = validate_system_xml_document(xml)
self.assertTrue(report.valid, report.as_dict())
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from PythonModels.components.amesim_pneumatic import (
from app.simulation.examples.test_mql.primitives.pneumatic import (
HELIUM_PNEUMATIC_GAS,
AmesimPneumaticGas,
AmesimPneumaticOrifice,
@@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from PythonModels.components.amesim_pneumatic_line import (
from app.simulation.examples.test_mql.primitives.pneumatic_lines import (
AmesimPnl0002Pipe,
AmesimPnl0003Pipe,
AmesimPnl00rPipe,
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from PythonModels.components.amesim_pneumatic_line import AmesimPnl0001Pipe
from app.simulation.examples.test_mql.primitives.pneumatic_lines import AmesimPnl0001Pipe
class AmesimPnl0001PipeTests(unittest.TestCase):
+2 -2
View File
@@ -37,7 +37,7 @@ def amesim_pnl0001_project() -> ReactFlowProjectPayload:
"k": 1.35,
"kth": 0.0,
"extemp": 300.0,
"gi": 1.0,
"gi": 0.0,
"mode": 2.0,
"p0": 300000.0,
"T0": 300.0,
@@ -50,7 +50,7 @@ def amesim_pnl0001_project() -> ReactFlowProjectPayload:
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.01, "le": 0.5, "rr": 1.0e-5, "gi": 1.0},
{"diam": 0.01, "le": 0.5, "rr": 1.0e-5, "gi": 0.0},
),
component_node(
"tank_1",
+2 -2
View File
@@ -38,7 +38,7 @@ def _dynamic_pipe_params() -> dict[str, float]:
"k": 1.35,
"kth": 0.0,
"extemp": 300.0,
"gi": 1.0,
"gi": 0.0,
"mode": 2.0,
"p0": 300000.0,
"T0": 300.0,
@@ -53,7 +53,7 @@ def _resistance_node(name: str) -> object:
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.01, "le": 0.5, "rr": 1.0e-5, "gi": 1.0},
{"diam": 0.01, "le": 0.5, "rr": 1.0e-5, "gi": 0.0},
)
+1 -1
View File
@@ -22,7 +22,7 @@ class AmesimPnl00rComponentTests(unittest.TestCase):
self.assertEqual(set(pipe.ports), {"port_1", "port_2"})
self.assertEqual(set(pipe.parameter_values), {"diam", "le", "rr", "gi"})
self.assertEqual(pipe.parameter_values["diam"], 0.01)
self.assertEqual(pipe.gi, 1)
self.assertEqual(pipe.gi, 0)
def test_rejects_fractional_gas_index(self) -> None:
with self.assertRaisesRegex(ValueError, "gi must be an integer"):
+1 -1
View File
@@ -30,7 +30,7 @@ def amesim_pnl00r_project() -> ReactFlowProjectPayload:
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.01, "le": 1.0, "rr": 1.0e-5, "gi": 1.0},
{"diam": 0.01, "le": 1.0, "rr": 1.0e-5, "gi": 0.0},
),
component_node(
"tank_1",
+1 -1
View File
@@ -35,7 +35,7 @@ def amesim_pnor001_project() -> ReactFlowProjectPayload:
"area": 5.0e-6,
"Cv": 0.5,
"Kv": 0.4,
"gi": 1.0,
"gi": 0.0,
"flowset": 1.0,
},
),
+1 -1
View File
@@ -29,7 +29,7 @@ def amesim_pnpl01_project() -> ReactFlowProjectPayload:
"kth": 0.0,
"sth": 0.1,
"extemp": 293.15,
"gi": 1.0,
"gi": 0.0,
"p0": 100000.0,
"T0": 293.15,
},
+2 -2
View File
@@ -35,7 +35,7 @@ def amesim_pnvo001_fixed_project() -> ReactFlowProjectPayload:
"area0": 5.0e-6,
"Cv": 0.5,
"Kv": 0.4,
"gi": 1.0,
"gi": 0.0,
"flowset": 1.0,
"opening": 0.5,
},
@@ -47,7 +47,7 @@ def amesim_pnvo001_fixed_project() -> ReactFlowProjectPayload:
physical_port("port_1", "bidirectional", "left"),
physical_port("port_2", "bidirectional", "right"),
],
{"diam": 0.02, "le": 1.0, "rr": 1.0e-5, "gi": 1.0},
{"diam": 0.02, "le": 1.0, "rr": 1.0e-5, "gi": 0.0},
),
component_node(
"tank_1",
+1 -1
View File
@@ -63,7 +63,7 @@ def amesim_pnvo001_signal_project() -> ReactFlowProjectPayload:
"area0": 5.0e-6,
"Cv": 0.5,
"Kv": 0.4,
"gi": 1.0,
"gi": 0.0,
"flowset": 1.0,
"opening0": 0.0,
},
+1 -1
View File
@@ -4,7 +4,7 @@ import unittest
from pathlib import Path
from unittest.mock import Mock
from PythonModels.reporting.amesim_results import (
from app.simulation.reporting.amesim_results import (
_resolve_result_members,
load_test_mql_amesim_results,
)
+1 -1
View File
@@ -71,7 +71,7 @@ def amesim_ud00_signal_project() -> ReactFlowProjectPayload:
"area0": 5.0e-6,
"Cv": 0.5,
"Kv": 0.4,
"gi": 1.0,
"gi": 0.0,
"flowset": 1.0,
"opening0": 0.0,
},
+116 -2
View File
@@ -9,6 +9,7 @@ from app.simulation.registry import (
COMPONENT_MODEL_REGISTRY,
validate_component_registries,
)
from app.simulation.core.metadata import ParameterDefinition
class ComponentCatalogTests(unittest.TestCase):
@@ -47,8 +48,58 @@ class ComponentCatalogTests(unittest.TestCase):
}
self.assertEqual(library["label"], "AMESim 组件库")
self.assertEqual([category["id"] for category in library["categories"]], ["storage", "flow", "junctions", "boundary", "signals", "mechanical"])
self.assertEqual(set(components), {"amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"})
self.assertEqual([category["id"] for category in library["categories"]], ["media", "storage", "flow", "junctions", "boundary", "signals", "mechanical"])
self.assertEqual(set(components), {"amesim_ideal_air_medium", "amesim_helium_medium", "amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"})
self.assertEqual(
components["amesim_ideal_air_medium"]["role"],
"amesimGasMediumDefinition",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["category"]["id"],
"media",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["label"],
"空气介质定义",
)
self.assertEqual(
components["amesim_ideal_air_medium"]["modelVersion"],
"0.2.0",
)
medium_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_ideal_air_medium"]["parameters"]
}
self.assertEqual(
list(medium_parameters),
["gi", "property_model"],
)
self.assertEqual(
medium_parameters["property_model"]["editor"],
"amesimGasPropertyModel",
)
self.assertEqual(
medium_parameters["property_model"]["options"],
[{"value": 0, "label": "理想气体"}],
)
helium_medium = components["amesim_helium_medium"]
self.assertEqual(helium_medium["role"], "amesimGasMediumDefinition")
self.assertEqual(helium_medium["category"]["id"], "media")
self.assertEqual(helium_medium["label"], "氦气介质定义")
self.assertEqual(helium_medium["modelVersion"], "0.1.0")
helium_parameters = {
parameter["name"]: parameter
for parameter in helium_medium["parameters"]
}
self.assertEqual(list(helium_parameters), ["gi", "property_model"])
self.assertEqual(
helium_parameters["property_model"]["editor"],
"amesimGasPropertyModel",
)
self.assertEqual(
helium_parameters["property_model"]["options"],
[{"value": 0, "label": "Peng–Robinson"}],
)
self.assertEqual(
[port["name"] for port in components["amesim_p4node2"]["ports"]],
["port_1", "port_2", "port_3", "port_4"],
@@ -174,6 +225,61 @@ class ComponentCatalogTests(unittest.TestCase):
self.assertEqual(parameters["p0"]["unit"], "Pa")
self.assertTrue(parameters["p0"]["minimumExclusive"])
def test_parameter_description_is_optional_interface_metadata(self) -> None:
described = ParameterDefinition(
"diam",
0.01,
label="管径",
quantity="length",
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="用于计算流通面积。",
).as_interface_dict()
undescribed = ParameterDefinition(
"gain",
1.0,
label="增益",
).as_interface_dict()
self.assertEqual(described["description"], "用于计算流通面积。")
self.assertEqual(described["default"], 0.01)
self.assertEqual(described["unit"], "m")
self.assertEqual(described["minimum"], 0.0)
self.assertTrue(described["minimumExclusive"])
self.assertNotIn("description", undescribed)
def test_catalog_exposes_descriptions_for_core_amesim_pneumatic_parameters(
self,
) -> None:
medium_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_helium_medium"]["parameters"]
}
chamber_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnch023"]["parameters"]
}
pipe_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnl00r"]["parameters"]
}
orifice_parameters = {
parameter["name"]: parameter
for parameter in self.components["amesim_pnor001"]["parameters"]
}
self.assertIn("自动分配", medium_parameters["gi"]["description"])
self.assertIn(
"Peng–Robinson",
medium_parameters["property_model"]["description"],
)
self.assertIn("固定有效容积", chamber_parameters["cvol"]["description"])
self.assertIn("Darcy", pipe_parameters["rr"]["description"])
self.assertIn("有效孔口面积", orifice_parameters["area"]["description"])
self.assertEqual(chamber_parameters["cvol"]["default"], 0.057)
self.assertEqual(pipe_parameters["diam"]["unit"], "m")
def test_catalog_adds_port_layout_without_changing_physical_contract(self) -> None:
tee_ports = {
port["name"]: port for port in self.components["tee"]["ports"]
@@ -200,6 +306,14 @@ class ComponentCatalogTests(unittest.TestCase):
self.assertEqual(schema["properties"]["schemaVersion"]["const"], 1)
self.assertIn("modelVersion", schema["$defs"]["component"]["required"])
self.assertIn("version", schema["$defs"]["library"]["required"])
parameter_schema = schema["$defs"]["parameter"]
self.assertEqual(
parameter_schema["properties"]["description"],
{"type": "string", "minLength": 1},
)
self.assertNotIn("description", parameter_schema["required"])
self.assertFalse(parameter_schema["additionalProperties"])
self.assertNotIn("unknownField", parameter_schema["properties"])
if __name__ == "__main__":
+99
View File
@@ -6,6 +6,7 @@ from app.simulation.components.experimental.library import LIBRARY
from app.simulation.components.amesim.library import LIBRARY as AMESIM_LIBRARY
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.metadata import ParameterDefinition, ParameterOption
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.ports import PortDefinition
from app.simulation.registry import (
@@ -30,6 +31,8 @@ class ComponentRegistryTests(unittest.TestCase):
"pipe",
"orifice",
"tee",
"amesim_ideal_air_medium",
"amesim_helium_medium",
"amesim_pnpl01",
"amesim_step0",
"amesim_ud00",
@@ -62,6 +65,11 @@ class ComponentRegistryTests(unittest.TestCase):
self.assertIn("amesim", libraries)
self.assertEqual(libraries["amesim"], AMESIM_LIBRARY)
self.assertEqual(
models["amesim_ideal_air_medium"].library.id,
"amesim",
)
self.assertEqual(models["amesim_helium_medium"].library.id, "amesim")
self.assertEqual(models["amesim_pnpl01"].library.id, "amesim")
self.assertEqual(models["amesim_step0"].library.id, "amesim")
self.assertEqual(models["amesim_ud00"].library.id, "amesim")
@@ -134,6 +142,97 @@ class ComponentRegistryTests(unittest.TestCase):
library=LIBRARY,
)
def test_property_model_parameter_options_are_strictly_validated(self) -> None:
def validate_parameter(parameter: ParameterDefinition) -> None:
class PropertyModelComponent(AlgebraicComponent):
MODEL_TYPE = "property_model_component"
MODEL_VERSION = "1.0.0"
PORTS = ()
PARAMETERS = (parameter,)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="物性模型测试组件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(),
)
@classmethod
def create(cls, *, name, medium, parameters):
del medium, parameters
return cls(name)
validate_component_model_class(
PropertyModelComponent,
library=LIBRARY,
)
def property_parameter(
*,
default: float = 0.0,
minimum: float | None = None,
maximum: float | None = None,
options: tuple[ParameterOption, ...] = (),
description: object = "",
) -> ParameterDefinition:
return ParameterDefinition(
"property_model",
default,
label="物性计算模型",
quantity="dimensionless",
unit="",
minimum=minimum,
maximum=maximum,
editor="amesimGasPropertyModel",
options=options,
description=description, # type: ignore[arg-type]
)
with self.assertRaisesRegex(ValueError, "description must be a string"):
validate_parameter(property_parameter(description=None))
with self.assertRaisesRegex(ValueError, "description must not be blank"):
validate_parameter(property_parameter(description=" "))
with self.assertRaisesRegex(ValueError, "must declare options"):
validate_parameter(property_parameter())
with self.assertRaisesRegex(ValueError, "duplicate option values"):
validate_parameter(
property_parameter(
options=(
ParameterOption(0, "理想气体"),
ParameterOption(0, "重复模型"),
)
)
)
with self.assertRaisesRegex(ValueError, "finite numbers"):
validate_parameter(
property_parameter(
options=(ParameterOption(float("nan"), "非法模型"),)
)
)
with self.assertRaisesRegex(ValueError, "default value must be one of 0"):
validate_parameter(
property_parameter(
default=1.0,
options=(ParameterOption(0, "理想气体"),),
)
)
with self.assertRaisesRegex(ValueError, "option 1 must be at most 0"):
validate_parameter(
property_parameter(
minimum=0.0,
maximum=0.0,
options=(ParameterOption(1, "越界模型"),),
)
)
with self.assertRaisesRegex(ValueError, "option values must be integers"):
validate_parameter(
property_parameter(
default=0.5,
options=(ParameterOption(0.5, "非整数模型"),),
)
)
if __name__ == "__main__":
unittest.main()
+4 -1
View File
@@ -3,10 +3,13 @@ import types
import unittest
from unittest.mock import patch
from PythonModels.core.solver import SolveIVPConfig, integrate_ode
from app.simulation.solvers.solver import SolveIVPConfig, integrate_ode
class IntegrateOdeTests(unittest.TestCase):
def test_generic_solver_keeps_canonical_default_tolerance(self) -> None:
self.assertEqual(SolveIVPConfig().atol, 1.0e-8)
def test_scipy_solver_receives_step_size_controls(self) -> None:
calls: list[dict[str, object]] = []
+505
View File
@@ -0,0 +1,505 @@
from __future__ import annotations
import json
from pathlib import Path
import unittest
from unittest.mock import patch
from app.simulation.components.experimental.library import LIBRARY
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.metadata import ParameterDefinition
from app.simulation.core.ports import PortDefinition
from app.simulation.registry import (
COMPONENT_MODEL_REGISTRY,
ComponentModelSpec,
validate_component_model_class,
)
from app.system_xml import validate_system_xml_document
GAS_REFERENCE_PARAMETER = ParameterDefinition(
"gi",
0.0,
label="气体类型索引",
minimum=0.0,
maximum=99.0,
editor="amesimGasReference",
)
class GasReferenceFixture(AlgebraicComponent):
MODEL_TYPE = "gas_reference_fixture"
MODEL_VERSION = "1.0.0"
PORTS = ()
PARAMETERS = (GAS_REFERENCE_PARAMETER,)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="气体引用测试组件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(),
)
def __init__(self, name: str, gi: float) -> None:
super().__init__(name)
self.set_parameter_values({"gi": gi})
@classmethod
def create(cls, *, name, medium, parameters):
del medium
return cls(name, parameters["gi"])
class GasMediumDefinitionFixture(AlgebraicComponent):
MODEL_TYPE = "gas_medium_definition_fixture"
MODEL_VERSION = "1.0.0"
PORTS = ()
PARAMETERS = (
ParameterDefinition(
"gi",
1.0,
label="介质索引",
minimum=1.0,
maximum=99.0,
),
)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="气体介质定义测试组件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(),
role="amesimGasMediumDefinition",
)
def __init__(self, name: str, gi: float) -> None:
super().__init__(name)
self.set_parameter_values({"gi": gi})
@classmethod
def create(cls, *, name, medium, parameters):
del medium
return cls(name, parameters["gi"])
class ConnectedGasReferenceFixture(AlgebraicComponent):
MODEL_TYPE = "connected_gas_reference_fixture"
MODEL_VERSION = "1.0.0"
PORTS = (
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
)
PARAMETERS = (GAS_REFERENCE_PARAMETER,)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="相连气体引用测试组件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(PortDisplaySpec("port_1", "right"),),
)
def __init__(self, name: str, gi: float) -> None:
super().__init__(name)
self.register_declared_port("port_1")
self.set_parameter_values({"gi": gi})
@classmethod
def create(cls, *, name, medium, parameters):
del medium
return cls(name, parameters["gi"])
GAS_REFERENCE_SPEC = ComponentModelSpec(
component_class=GasReferenceFixture,
library=LIBRARY,
)
GAS_MEDIUM_DEFINITION_SPEC = ComponentModelSpec(
component_class=GasMediumDefinitionFixture,
library=LIBRARY,
)
CONNECTED_GAS_REFERENCE_SPEC = ComponentModelSpec(
component_class=ConnectedGasReferenceFixture,
library=LIBRARY,
)
def system_xml(
*,
gi: float | None,
medium_reference_version: str | None = None,
medium_gas_indices: tuple[float, ...] = (),
) -> bytes:
version_attribute = (
f' mediumReferenceVersion="{medium_reference_version}"'
if medium_reference_version is not None
else ""
)
reference_parameter = (
f'<Parameter name="gi" value="{gi}"/>' if gi is not None else ""
)
medium_components = "".join(
f"""
<Component id="medium_{index}" name="medium_{index}"
type="gas_medium_definition_fixture"
componentType="gas_medium_definition_fixture"
x="0" y="0">
<Parameter name="gi" value="{gas_index}"/>
</Component>"""
for index, gas_index in enumerate(medium_gas_indices, start=1)
)
return f"""<?xml version="1.0" encoding="UTF-8"?>
<System name="medium-contract" schemaVersion="2" unitSystem="SI"{version_attribute}>
<Simulation tStart="0" tStop="1" step="0.1" maxStep="0.01" method="BDF"/>
<Components>
<Component id="consumer_1" name="consumer_1"
type="gas_reference_fixture"
componentType="gas_reference_fixture"
x="0" y="0">
{reference_parameter}
</Component>{medium_components}
</Components>
<Connections/>
</System>
""".encode()
def connected_system_xml(
*,
first_gi: float,
second_gi: float,
medium_gas_indices: tuple[float, ...],
) -> bytes:
medium_components = "".join(
f"""
<Component id="medium_{index}" name="medium_{index}"
type="gas_medium_definition_fixture"
componentType="gas_medium_definition_fixture"
x="0" y="0">
<Parameter name="gi" value="{gas_index}"/>
</Component>"""
for index, gas_index in enumerate(medium_gas_indices, start=1)
)
return f"""<?xml version="1.0" encoding="UTF-8"?>
<System name="connected-medium-contract" schemaVersion="2" unitSystem="SI"
mediumReferenceVersion="1">
<Simulation tStart="0" tStop="1" step="0.1" maxStep="0.01" method="BDF"/>
<Components>
<Component id="consumer_1" name="consumer_1"
type="connected_gas_reference_fixture"
componentType="connected_gas_reference_fixture"
x="0" y="0">
<Port name="port_1" kind="physical" domain="pneumatic"
nominalRole="bidirectional" positiveFlowDirection="intoComponent"
side="right"/>
<Parameter name="gi" value="{first_gi}"/>
</Component>
<Component id="consumer_2" name="consumer_2"
type="connected_gas_reference_fixture"
componentType="connected_gas_reference_fixture"
x="100" y="0">
<Port name="port_1" kind="physical" domain="pneumatic"
nominalRole="bidirectional" positiveFlowDirection="intoComponent"
side="left"/>
<Parameter name="gi" value="{second_gi}"/>
</Component>{medium_components}
</Components>
<Connections>
<Connection id="gas_connection" kind="physical" domain="pneumatic">
<Endpoint component="consumer_1" port="port_1"/>
<Endpoint component="consumer_2" port="port_1"/>
</Connection>
</Connections>
</System>
""".encode()
def air_medium_system_xml(*, property_model: float | None) -> bytes:
property_parameter = (
f'<Parameter name="property_model" value="{property_model}"/>'
if property_model is not None
else ""
)
return f"""<?xml version="1.0" encoding="UTF-8"?>
<System name="air-medium-contract" schemaVersion="2" unitSystem="SI"
mediumReferenceVersion="1">
<Simulation tStart="0" tStop="1" step="0.1" maxStep="0.01" method="BDF"/>
<Components>
<Component id="air_1" name="air_1"
type="amesim_ideal_air_medium"
componentType="amesim_ideal_air_medium"
x="0" y="0">
<Parameter name="gi" value="1"/>
{property_parameter}
</Component>
</Components>
<Connections/>
</System>
""".encode()
class MediumReferenceCatalogContractTests(unittest.TestCase):
def test_registry_emits_parameter_editor_and_component_role(self) -> None:
validate_component_model_class(GasReferenceFixture, library=LIBRARY)
validate_component_model_class(GasMediumDefinitionFixture, library=LIBRARY)
reference_payload = GAS_REFERENCE_SPEC.as_catalog_dict()
definition_payload = GAS_MEDIUM_DEFINITION_SPEC.as_catalog_dict()
self.assertEqual(
reference_payload["parameters"][0]["editor"],
"amesimGasReference",
)
self.assertEqual(
definition_payload["role"],
"amesimGasMediumDefinition",
)
def test_committed_catalog_schema_constrains_the_extension_values(self) -> None:
schema_path = (
Path(__file__).resolve().parent.parent
/ "schemas"
/ "component-catalog-v1.schema.json"
)
schema = json.loads(schema_path.read_text(encoding="utf-8"))
self.assertEqual(
schema["$defs"]["parameter"]["properties"]["editor"]["enum"],
["amesimGasReference", "amesimGasPropertyModel"],
)
self.assertEqual(
schema["$defs"]["parameter"]["properties"]["options"]["items"][
"$ref"
],
"#/$defs/parameterOption",
)
self.assertEqual(
schema["$defs"]["component"]["properties"]["role"]["enum"],
["amesimGasMediumDefinition"],
)
class MediumReferenceSystemXmlContractTests(unittest.TestCase):
def setUp(self) -> None:
self.registry_patch = patch.dict(
COMPONENT_MODEL_REGISTRY,
{
GasReferenceFixture.MODEL_TYPE: GAS_REFERENCE_SPEC,
GasMediumDefinitionFixture.MODEL_TYPE: GAS_MEDIUM_DEFINITION_SPEC,
ConnectedGasReferenceFixture.MODEL_TYPE: CONNECTED_GAS_REFERENCE_SPEC,
},
)
self.registry_patch.start()
self.addCleanup(self.registry_patch.stop)
def test_legacy_gi_one_without_definition_maps_to_builtin_air(self) -> None:
report = validate_system_xml_document(system_xml(gi=1.0))
self.assertTrue(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"AMESIM_GI_LEGACY_AIR_MAPPED"},
)
assert report.document is not None
self.assertEqual(report.document.medium_reference_version, "1")
self.assertEqual(
report.document.as_project_data()["mediumReferenceVersion"],
1,
)
self.assertEqual(
report.document.as_project_data()["nodes"][0]["data"]["parameters"]["gi"],
0.0,
)
def test_legacy_missing_gi_injects_builtin_air(self) -> None:
report = validate_system_xml_document(system_xml(gi=None))
self.assertTrue(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"AMESIM_GI_DEFAULT_INJECTED"},
)
assert report.document is not None
self.assertEqual(
report.document.as_project_data()["nodes"][0]["data"]["parameters"]["gi"],
0.0,
)
def test_versioned_gi_one_is_not_reinterpreted(self) -> None:
report = validate_system_xml_document(
system_xml(gi=1.0, medium_reference_version="1")
)
self.assertFalse(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"AMESIM_GAS_REFERENCE_UNDEFINED"},
)
assert report.document is not None
self.assertEqual(report.document.medium_reference_version, "1")
self.assertEqual(
report.document.as_project_data()["nodes"][0]["data"]["parameters"]["gi"],
1.0,
)
def test_legacy_document_with_definition_is_not_reinterpreted(self) -> None:
report = validate_system_xml_document(
system_xml(gi=1.0, medium_gas_indices=(1.0,))
)
self.assertTrue(report.valid)
self.assertEqual(report.issues, ())
assert report.document is not None
self.assertEqual(
report.document.as_project_data()["nodes"][0]["data"]["parameters"]["gi"],
1.0,
)
def test_versioned_positive_reference_requires_matching_definition(self) -> None:
report = validate_system_xml_document(
system_xml(
gi=2.0,
medium_reference_version="1",
medium_gas_indices=(2.0,),
)
)
self.assertTrue(report.valid)
self.assertEqual(report.issues, ())
def test_missing_property_model_is_defaulted_for_versioned_medium(self) -> None:
report = validate_system_xml_document(
air_medium_system_xml(property_model=None)
)
self.assertTrue(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"AMESIM_GAS_PROPERTY_MODEL_DEFAULTED"},
)
assert report.document is not None
parameters = report.document.as_project_data()["nodes"][0]["data"][
"parameters"
]
self.assertEqual(parameters["gi"], 1.0)
self.assertEqual(parameters["property_model"], 0.0)
def test_explicit_supported_property_model_round_trips_without_warning(self) -> None:
report = validate_system_xml_document(
air_medium_system_xml(property_model=0.0)
)
self.assertTrue(report.valid)
self.assertEqual(report.issues, ())
def test_unsupported_property_model_is_rejected_semantically(self) -> None:
for property_model in (1.0, 99.0):
with self.subTest(property_model=property_model):
report = validate_system_xml_document(
air_medium_system_xml(property_model=property_model)
)
self.assertFalse(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"PARAMETER_VALUE_INVALID"},
)
def test_medium_definition_index_must_be_integer_in_range(self) -> None:
fractional = validate_system_xml_document(
system_xml(
gi=0.0,
medium_reference_version="1",
medium_gas_indices=(1.5,),
)
)
zero = validate_system_xml_document(
system_xml(
gi=0.0,
medium_reference_version="1",
medium_gas_indices=(0.0,),
)
)
self.assertIn(
"AMESIM_GAS_MEDIUM_INDEX_INVALID",
{issue.code for issue in fractional.issues},
)
self.assertIn(
"AMESIM_GAS_MEDIUM_INDEX_INVALID",
{issue.code for issue in zero.issues},
)
def test_medium_definition_indices_must_be_unique(self) -> None:
report = validate_system_xml_document(
system_xml(
gi=1.0,
medium_reference_version="1",
medium_gas_indices=(1.0, 1.0),
)
)
self.assertFalse(report.valid)
self.assertIn(
"AMESIM_GAS_MEDIUM_INDEX_DUPLICATE",
{issue.code for issue in report.issues},
)
def test_gas_reference_index_must_be_integer_in_range(self) -> None:
fractional = validate_system_xml_document(
system_xml(gi=1.5, medium_reference_version="1")
)
out_of_range = validate_system_xml_document(
system_xml(gi=100.0, medium_reference_version="1")
)
self.assertIn(
"AMESIM_GAS_REFERENCE_INDEX_INVALID",
{issue.code for issue in fractional.issues},
)
self.assertIn(
"AMESIM_GAS_REFERENCE_INDEX_INVALID",
{issue.code for issue in out_of_range.issues},
)
def test_connected_pneumatic_references_cannot_conflict(self) -> None:
report = validate_system_xml_document(
connected_system_xml(
first_gi=1.0,
second_gi=2.0,
medium_gas_indices=(1.0, 2.0),
)
)
self.assertFalse(report.valid)
self.assertEqual(
{issue.code for issue in report.issues},
{"AMESIM_GAS_REFERENCE_CONFLICT"},
)
def test_connected_pneumatic_references_may_share_one_medium(self) -> None:
report = validate_system_xml_document(
connected_system_xml(
first_gi=1.0,
second_gi=1.0,
medium_gas_indices=(1.0,),
)
)
self.assertTrue(report.valid)
self.assertEqual(report.issues, ())
def test_unknown_medium_reference_version_is_rejected_by_xsd(self) -> None:
report = validate_system_xml_document(
system_xml(gi=0.0, medium_reference_version="2")
)
self.assertFalse(report.valid)
self.assertIsNone(report.document)
self.assertEqual({issue.code for issue in report.issues}, {"XSD_VALIDATION_ERROR"})
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from PythonModels.core.peng_robinson import AIR_PR, HELIUM_PR, NITROGEN_PR, PengRobinsonFluid
from app.simulation.core.peng_robinson import AIR_PR, HELIUM_PR, NITROGEN_PR, PengRobinsonFluid
class PengRobinsonTest(unittest.TestCase):
@@ -0,0 +1,65 @@
from __future__ import annotations
import unittest
from app.main import (
ReactFlowProjectPayload,
build_reactflow_system_xml,
pydantic_to_jsonable,
)
class ReactFlowProjectMediumReferenceVersionTests(unittest.TestCase):
def test_version_one_is_accepted_and_serialized(self) -> None:
project = ReactFlowProjectPayload(
name="versioned-medium-references",
mediumReferenceVersion=1,
)
self.assertEqual(project.mediumReferenceVersion, 1)
self.assertEqual(
pydantic_to_jsonable(project)["mediumReferenceVersion"],
1,
)
def test_other_medium_reference_versions_are_rejected(self) -> None:
for version in (0, 2, -1, "1"):
with self.subTest(version=version):
with self.assertRaises(ValueError):
ReactFlowProjectPayload(
name="unsupported-medium-references",
mediumReferenceVersion=version,
)
def test_legacy_project_without_version_remains_compatible(self) -> None:
project = ReactFlowProjectPayload(name="legacy-project")
self.assertIsNone(project.mediumReferenceVersion)
def test_xml_export_adds_default_property_model_to_legacy_medium_node(self) -> None:
project = ReactFlowProjectPayload(
name="legacy-medium-node",
mediumReferenceVersion=1,
nodes=[
{
"id": "air_1",
"position": {"x": 10, "y": 20},
"data": {
"label": "Air 1",
"componentType": "amesim_ideal_air_medium",
"modelType": "amesim_ideal_air_medium",
"ports": [],
"parameters": {"gi": 1},
},
}
],
)
xml = build_reactflow_system_xml(project).decode("utf-8")
self.assertIn('<Parameter name="gi" value="1"', xml)
self.assertIn('<Parameter name="property_model" value="0.0"', xml)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -4,7 +4,7 @@ import tempfile
import unittest
from pathlib import Path
from PythonModels.scripts.run_test_mql_baseline import (
from app.simulation.examples.test_mql.run_baseline import (
TestMqlBaselineExecutionConfig,
TestMqlBaselinePathConfig,
TestMqlBaselineScriptConfig,
@@ -4,10 +4,10 @@ import tempfile
import unittest
from pathlib import Path
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.systems.test_mql import TestMqlPnl0001LineRhsDiagnostic
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.examples.test_mql.system import TestMqlPnl0001LineRhsDiagnostic
from PythonModels.scripts.run_test_mql_full_state_comparison import (
from app.simulation.examples.test_mql.run_full_state_comparison import (
TestMqlFullStateComparisonExecutionConfig,
TestMqlFullStateComparisonPathConfig,
TestMqlFullStateComparisonScriptConfig,
@@ -43,7 +43,7 @@ class RunTestMqlFullStateComparisonScriptTests(unittest.TestCase):
def test_ideal_pn2vol_reference_dtemp_uses_internal_energy_mass_offset(
self,
) -> None:
from PythonModels.components.amesim_pneumatic import HELIUM_PNEUMATIC_GAS
from app.simulation.examples.test_mql.primitives.pneumatic import HELIUM_PNEUMATIC_GAS
gas = HELIUM_PNEUMATIC_GAS
result = _ideal_pn2vol_reference_dtemp(
+1
View File
@@ -87,6 +87,7 @@ class SystemXmlProtocolTests(unittest.TestCase):
self.assertEqual(root.attrib["name"], "protocol-test")
self.assertEqual(root.attrib["schemaVersion"], "2")
self.assertEqual(root.attrib["unitSystem"], "SI")
self.assertEqual(root.attrib["mediumReferenceVersion"], "1")
self.assertEqual(
[child.tag for child in root],
["Simulation", "Components", "Connections"],
+1 -1
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.systems.test_mql_baseline import run_test_mql_baseline_passthrough
from app.simulation.examples.test_mql.baseline import run_test_mql_baseline_passthrough
REPO_ROOT = Path(__file__).resolve().parents[1]
+2 -2
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
import unittest
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.systems.test_mql import TestMqlSystem
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.examples.test_mql.system import TestMqlSystem
class TestMqlPneumaticBranchSimulationTests(unittest.TestCase):
+2 -2
View File
@@ -3,8 +3,8 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_chamber_observations import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_chamber_observations import (
build_test_mql_chamber_observation_catalog,
)
+2 -2
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
import unittest
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.systems.test_mql import TestMqlSystem
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.examples.test_mql.system import TestMqlSystem
class TestMqlPneumaticChamberSegmentTests(unittest.TestCase):
+2 -2
View File
@@ -2,11 +2,11 @@ from __future__ import annotations
import unittest
from PythonModels.components.amesim_pneumatic import (
from app.simulation.examples.test_mql.primitives.pneumatic import (
AmesimPneumaticOrifice,
AmesimPneumaticVolume,
)
from PythonModels.systems.test_mql_closure import (
from app.simulation.examples.test_mql.closure import (
TestMqlPneumaticBranchComponents,
TestMqlPneumaticClosure,
)
+2 -2
View File
@@ -4,8 +4,8 @@ import tempfile
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_comparison import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_comparison import (
DEFAULT_TEST_MQL_ALIGNMENT_PATHS,
TestMqlComparisonError,
compare_test_mql_series,
+1 -1
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.systems.test_mql_computed import (
from app.simulation.examples.test_mql.computed import (
run_test_mql_computed_geometry,
run_test_mql_computed_line_relations,
run_test_mql_computed_mechanical_relations,
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from PythonModels.systems.test_mql_config import TestMqlConfig, resolve_numeric_expression
from app.simulation.examples.test_mql.config import TestMqlConfig, resolve_numeric_expression
class TestMqlConfigTest(unittest.TestCase):
+5 -1
View File
@@ -6,11 +6,15 @@ from pathlib import Path
from app.main import ReactFlowProjectPayload, run_reactflow_test_mql
from app.simulation.examples.test_mql.run import prepare_test_mql_run, run_test_mql
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.examples.test_mql.system import TestMqlRunConfig
from app.simulation.paths import PROJECT_ROOT, SIMULATION_RUNS_DIR
from PythonModels.systems.test_mql import TestMqlRunConfig
class TestMqlExampleRunnerTests(unittest.TestCase):
def test_test_mql_keeps_calibrated_default_tolerance(self) -> None:
self.assertEqual(SolveIVPConfig().atol, 1.0e-10)
def test_default_run_paths_use_app_simulation_runs_dir(self) -> None:
prepared = prepare_test_mql_run()
+2 -2
View File
@@ -3,8 +3,8 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_line_observations import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_line_observations import (
G_PER_S_TO_KG_PER_S,
build_test_mql_line_observation_catalog,
)
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_variables import build_test_mql_variable_catalog
from PythonModels.systems.test_mql_lines import build_test_mql_line_assembly
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_variables import build_test_mql_variable_catalog
from app.simulation.examples.test_mql.lines import build_test_mql_line_assembly
REPO_ROOT = Path(__file__).resolve().parents[1]
+5 -5
View File
@@ -3,11 +3,11 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.reporting.test_mql_variables import build_test_mql_variable_catalog
from PythonModels.systems.test_mql import TestMqlSystem
from PythonModels.systems.test_mql_mechanical import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.reporting.test_mql_variables import build_test_mql_variable_catalog
from app.simulation.examples.test_mql.system import TestMqlSystem
from app.simulation.examples.test_mql.mechanical import (
TestMqlMechanicalMassClosure,
build_test_mql_mechanical_assembly,
circular_area,
@@ -3,8 +3,8 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_mechanical_observations import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_mechanical_observations import (
build_test_mql_mechanical_observation_catalog,
)
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql import TestMqlSystem
from PythonModels.systems.test_mql_nodes import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.system import TestMqlSystem
from app.simulation.examples.test_mql.nodes import (
build_test_mql_node3_assembly,
build_test_mql_node4_assembly,
)
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_comparison import compare_test_mql_series
from PythonModels.reporting.test_mql_observations import build_test_mql_observation_catalog
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_comparison import compare_test_mql_series
from app.simulation.reporting.test_mql_observations import build_test_mql_observation_catalog
REPO_ROOT = Path(__file__).resolve().parents[1]
+3 -3
View File
@@ -3,12 +3,12 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_orifice_observations import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_orifice_observations import (
G_PER_S_TO_KG_PER_S,
build_test_mql_orifice_observation_catalog,
)
from PythonModels.systems.test_mql_pneumatic import (
from app.simulation.examples.test_mql.pneumatic import (
TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
)
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_observations import build_test_mql_observation_catalog
from PythonModels.reporting.test_mql_output_schema import build_test_mql_output_schema
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_observations import build_test_mql_observation_catalog
from app.simulation.reporting.test_mql_output_schema import build_test_mql_output_schema
REPO_ROOT = Path(__file__).resolve().parents[1]
+4 -4
View File
@@ -3,10 +3,10 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_observations import build_test_mql_observation_catalog
from PythonModels.reporting.test_mql_output_schema import build_test_mql_output_schema
from PythonModels.reporting.test_mql_output_validation import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_observations import build_test_mql_observation_catalog
from app.simulation.reporting.test_mql_output_schema import build_test_mql_output_schema
from app.simulation.reporting.test_mql_output_validation import (
TestMqlOutputValidationError,
compare_validated_test_mql_output,
validate_test_mql_output,
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.components.amesim_pneumatic import HELIUM_PNEUMATIC_GAS, cm3_to_m3
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql_pneumatic import (
from app.simulation.examples.test_mql.primitives.pneumatic import HELIUM_PNEUMATIC_GAS, cm3_to_m3
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.pneumatic import (
TEST_MQL_PNVO001_FLOW_COEFFICIENT_MULTIPLIER,
absolute_pressure_from_amesim_bar_parameter,
build_test_mql_pneumatic_assembly,
+3 -3
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql_line_parameters import load_test_mql_pnl0001_specs
from PythonModels.systems.test_mql_pneumatic_lines import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.line_parameters import load_test_mql_pnl0001_specs
from app.simulation.examples.test_mql.pneumatic_lines import (
TEST_MQL_PNL0001_D20_L1_LINEAR_CONDUCTANCE,
build_test_mql_pnl0001_assembly,
)
+5 -5
View File
@@ -3,11 +3,11 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.core.solver import SolveIVPConfig
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_output_schema import build_test_mql_output_schema
from PythonModels.reporting.test_mql_output_validation import compare_validated_test_mql_output
from PythonModels.systems.test_mql import TestMqlSystem
from app.simulation.examples.test_mql.solver import SolveIVPConfig
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_output_schema import build_test_mql_output_schema
from app.simulation.reporting.test_mql_output_validation import compare_validated_test_mql_output
from app.simulation.examples.test_mql.system import TestMqlSystem
REPO_ROOT = Path(__file__).resolve().parents[1]
+4 -4
View File
@@ -3,10 +3,10 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql import TestMqlSystem
from PythonModels.systems.test_mql_line_parameters import load_test_mql_pnl0002_specs
from PythonModels.systems.test_mql_pneumatic_lines import build_test_mql_pnl0002_assembly
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.system import TestMqlSystem
from app.simulation.examples.test_mql.line_parameters import load_test_mql_pnl0002_specs
from app.simulation.examples.test_mql.pneumatic_lines import build_test_mql_pnl0002_assembly
REPO_ROOT = Path(__file__).resolve().parents[1]
+4 -4
View File
@@ -3,13 +3,13 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.systems.test_mql import TestMqlSystem
from PythonModels.systems.test_mql_line_parameters import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.examples.test_mql.system import TestMqlSystem
from app.simulation.examples.test_mql.line_parameters import (
load_test_mql_pnl0003_specs,
load_test_mql_pnl00r_specs,
)
from PythonModels.systems.test_mql_pneumatic_lines import (
from app.simulation.examples.test_mql.pneumatic_lines import (
build_test_mql_pnl0003_assembly,
build_test_mql_pnl00r_assembly,
)
+2 -2
View File
@@ -2,12 +2,12 @@ from __future__ import annotations
import unittest
from PythonModels.components.amesim_pneumatic import (
from app.simulation.examples.test_mql.primitives.pneumatic import (
AmesimPneumaticOrifice,
AmesimPneumaticVolume,
AmesimVariablePneumaticVolume,
)
from PythonModels.systems.test_mql import GLOBAL_PARAMETERS, SUBMODEL_COUNTS, TestMqlSystem
from app.simulation.examples.test_mql.system import GLOBAL_PARAMETERS, SUBMODEL_COUNTS, TestMqlSystem
class TestMqlStructuralTest(unittest.TestCase):
+1 -1
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.systems.test_mql_topology import load_test_mql_cir_topology
from app.simulation.examples.test_mql.topology import load_test_mql_cir_topology
REPO_ROOT = Path(__file__).resolve().parents[1]
+2 -2
View File
@@ -3,8 +3,8 @@ from __future__ import annotations
import unittest
from pathlib import Path
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
from PythonModels.reporting.test_mql_variables import (
from app.simulation.reporting.amesim_results import load_test_mql_amesim_results
from app.simulation.reporting.test_mql_variables import (
build_test_mql_variable_catalog,
split_data_path,
)