验收四路模型并优化拓扑求解性能

This commit is contained in:
huojiarong committed 2026-08-12 11:57:42 +00:00
1 parent caca32a513
commit 456c29b3b6
20 files changed
+1085 -137

No files matched your search

+37
View File
@@ -93,6 +93,21 @@ class AmesimHeliumPengRobinsonMediumTests(unittest.TestCase):
delta=1.0e-8,
)
medium.temperature_from_pressure_enthalpy.cache_clear()
first = medium.temperature_from_pressure_enthalpy(
pressure,
transport_enthalpy,
)
after_first = medium.temperature_from_pressure_enthalpy.cache_info()
second = medium.temperature_from_pressure_enthalpy(
pressure,
transport_enthalpy,
)
after_second = medium.temperature_from_pressure_enthalpy.cache_info()
self.assertEqual(second, first)
self.assertEqual(after_first.misses, 1)
self.assertEqual(after_second.hits, 1)
density = medium.density(pressure, temperature)
volume = 0.01
properties = medium.properties_from_mU(
@@ -120,6 +135,28 @@ class AmesimHeliumPengRobinsonMediumTests(unittest.TestCase):
with self.assertRaises(RecoverableTrialStateError):
medium.properties_from_mU(-1.0, 1.0, 1.0)
def test_exact_repeated_real_gas_state_recovery_is_cached(self) -> None:
medium = AmesimHeliumPengRobinsonMedium()
pressure = 15.3e6
temperature = 293.15
volume = 0.057
mass = medium.density(pressure, temperature) * volume
internal_energy = mass * medium.specific_internal_energy_at_pressure(
pressure,
temperature,
)
first = medium.properties_from_mU(mass, internal_energy, volume)
second = medium.properties_from_mU(mass, internal_energy, volume)
changed = medium.properties_from_mU(
mass,
internal_energy,
volume * 1.01,
)
self.assertIs(second, first)
self.assertIsNot(changed, first)
def test_amesim_2404_real_gas_isentropic_factor_reference(self) -> None:
medium = AmesimHeliumPengRobinsonMedium()
@@ -207,10 +207,11 @@ class AmesimMechanicalPublicComponentTests(unittest.TestCase):
residuals = node.pressure_flow_equation_residuals()
self.assertEqual(node.active_ports, ("port_1", "port_2", "port_9"))
self.assertAlmostEqual(node.total_force, 0.0)
self.assertAlmostEqual(node.total_force, 10.0)
self.assertAlmostEqual(node.force_balance, 0.0)
self.assertEqual(len(residuals), 5)
self.assertTrue(all(abs(residual.value) <= 1.0e-12 for residual in residuals))
self.assertEqual(node.component_result_values(), {"tforce": 0.0})
self.assertEqual(node.component_result_values(), {"tforce": 10.0})
def test_lmechn1_registry_rejects_fractional_integer_options(self) -> None:
with self.assertRaisesRegex(ValueError, "v1 must be an integer"):
+11
View File
@@ -10,6 +10,7 @@ from app.main import (
reactflow_project_storage_data,
run_system_xml_simulation,
)
from app.simulation.systems.generic import GenericFluidSystem
from app.system_xml import validate_system_xml_document
from tests.test_amesim_pnvo001_signal_xml import signal_edge, signal_port
from tests.test_generic_system_xml_simulation import component_node, physical_edge
@@ -287,6 +288,16 @@ def force_node_mass_project() -> ReactFlowProjectPayload:
class AmesimMechanicalXmlTests(unittest.TestCase):
def test_mechanical_states_use_machine_scale_absolute_tolerances(self) -> None:
system = GenericFluidSystem(
compile_reactflow_network(zero_force_mass_project())
)
tolerances = system.mechanical_state_reducer.absolute_tolerances(1.0e-8)
self.assertEqual(tolerances, [1.0e-12, 1.0e-12])
self.assertEqual(len(tolerances), len(system.initial_state_vector()))
def test_sparse_legacy_reactflow_mecmas_defaults_are_canonicalized(
self,
@@ -1,6 +1,7 @@
from __future__ import annotations
import unittest
from math import log10
from app.simulation.components.amesim.flow.pipes import AmesimPnl0002, AmesimPnl0003
from app.simulation.core.medium import IdealGasMedium
@@ -45,6 +46,20 @@ class AmesimPnl0002ComponentTests(unittest.TestCase):
self.assertGreater(forward, 0.0)
self.assertLess(reverse, 0.0)
def test_exact_repeated_pipe_flow_inversion_is_cached(self) -> None:
pipe = AmesimPnl0002("pnl_2", self.medium, p0=100000.0, T0=293.15)
pipe._one_way_pn2pipefr_mass_flow.cache_clear()
first = pipe.mass_flow(15.3e6, 10.0e6, 293.15)
after_first = pipe._one_way_pn2pipefr_mass_flow.cache_info()
second = pipe.mass_flow(15.3e6, 10.0e6, 293.15)
after_second = pipe._one_way_pn2pipefr_mass_flow.cache_info()
self.assertEqual(second, first)
self.assertEqual(after_first.misses, 1)
self.assertEqual(after_second.misses, 1)
self.assertEqual(after_second.hits, 1)
def test_external_upstream_flow_uses_connected_stream_temperature(self) -> None:
pipe = AmesimPnl0002("pnl_2", self.medium, p0=100000.0, T0=350.0)
center = pipe.properties()
@@ -78,6 +93,23 @@ class AmesimPnl0002ComponentTests(unittest.TestCase):
self.assertAlmostEqual(flow, expected)
self.assertNotAlmostEqual(flow, center_temperature_flow)
def test_fully_rough_limit_matches_nikuradse_colebrook_asymptote(self) -> None:
relative_roughness = 0.045 / 20.0
pipe = AmesimPnl0002(
"pnl_2",
self.medium,
diam=0.02,
le=2.0,
rr=relative_roughness,
)
expected = 1.0 / (-2.0 * log10(relative_roughness / 3.7)) ** 2
self.assertAlmostEqual(
pipe.friction_factor(1.0e12),
expected,
delta=1.0e-10,
)
def test_missing_stream_cache_preserves_center_temperature_fallback(self) -> None:
pipe = AmesimPnl0002("pnl_2", self.medium, p0=100000.0, T0=350.0)
center = pipe.properties()
+1 -1
View File
@@ -83,7 +83,7 @@ class AmesimPnl00rComponentTests(unittest.TestCase):
self.assertAlmostEqual(
pipe_20mm.friction_factor(56_887.5547),
0.0215740061043,
delta=8.0e-5,
delta=1.0e-4,
)
self.assertAlmostEqual(
pipe_20mm.friction_factor(700_686.41),
+16
View File
@@ -52,6 +52,22 @@ class AmesimPnor001ComponentTests(unittest.TestCase):
self.assertEqual(residuals["pressure_flow_relation"].value, 0.0)
self.assertEqual(set(orifice.component_result_values()), {"cm", "gasvel"})
def test_exact_repeated_flow_characteristics_are_cached(self) -> None:
orifice = AmesimPnor001("pnor_1", self.medium)
orifice._one_way_flow_characteristics.cache_clear()
first = orifice._one_way_flow_characteristics(
upstream_pressure=500000.0,
downstream_pressure=100000.0,
upstream_temperature=293.15,
)
after_first = orifice._one_way_flow_characteristics.cache_info()
second = orifice._one_way_flow_characteristics(
upstream_pressure=500000.0,
downstream_pressure=100000.0,
upstream_temperature=293.15,
)
def test_mass_flow_is_bidirectional_by_pressure_order(self) -> None:
orifice = AmesimPnor001("pnor_1", self.medium)
+5 -3
View File
@@ -464,9 +464,11 @@ class ComponentCatalogTests(unittest.TestCase):
with self.subTest(model_type=model_type):
component = self.components[model_type]
model_parameters = parameters(model_type)
expected_version = (
"0.5.0" if model_type == "amesim_pnl0002" else "0.3.0"
)
expected_version = {
"amesim_pnl0001": "0.4.0",
"amesim_pnl0002": "0.6.0",
"amesim_pnl0003": "0.4.0",
}[model_type]
self.assertEqual(component["modelVersion"], expected_version)
self.assertEqual(model_parameters["mode"]["editor"], "choice")
self.assertEqual(model_parameters["mode"]["options"], thermal_options)
@@ -488,6 +488,36 @@ class MechanicalSolverCausalizationTests(unittest.TestCase):
for value in result.series["mass.a"]:
self.assertAlmostEqual(value, 0.0, places=12)
def test_ideal_stop_ignores_zero_velocity_roundoff_inside_boundary_band(self) -> None:
system, _mass = _single_mass_system(
0.0,
stoptype=1.0,
x0=0.0,
xmin=0.0,
xmax=1.0,
)
reducer = system.mechanical_state_reducer
previous_state = [0.0, 2.0e-32]
current_state = [0.0, -1.0e-32]
def dense_state(time: float) -> list[float]:
fraction = float(time)
return [
0.0,
previous_state[1]
+ fraction * (current_state[1] - previous_state[1]),
]
transition = reducer.state_transition(
0.0,
previous_state,
1.0,
current_state,
dense_state,
)
self.assertIsNone(transition)
def test_ideal_upper_stop_projects_a_high_speed_impact(self) -> None:
system, _mass = _single_mass_system(
100.0,
+108
View File
@@ -0,0 +1,108 @@
from __future__ import annotations
import unittest
from app.simulation.components.amesim.flow.pipes import AmesimPnl0002
from app.simulation.core.medium import IdealGasMedium
from app.simulation.reporting.amesim_results import AmesimResults
from app.simulation.reporting.pnl0002_replay import (
Pnl0002ReplayPaths,
replay_pnl0002_amesim_states,
)
class Pnl0002ReplayTests(unittest.TestCase):
def setUp(self) -> None:
self.pipe = AmesimPnl0002(
"pneumatic_83",
IdealGasMedium(),
diam=0.02,
le=2.0,
rr=0.00225,
)
self.paths = Pnl0002ReplayPaths(
center_pressure="center_p",
center_temperature="center_T",
port_1_pressure="port_1_p",
port_1_temperature="port_1_T",
port_1_mass_flow="port_1_dm",
port_2_pressure="port_2_p",
port_2_temperature="port_2_T",
port_2_mass_flow="port_2_dm",
reynolds="re",
friction_factor="ff",
)
def _matching_results(self) -> AmesimResults:
center_pressure = 300000.0
center_temperature = 320.0
port_1_pressure = 500000.0
port_1_temperature = 330.0
port_2_pressure = 100000.0
port_2_temperature = 310.0
flow_1 = self.pipe.mass_flow(
port_1_pressure,
center_pressure,
port_1_temperature,
)
flow_2 = self.pipe.mass_flow(
port_2_pressure,
center_pressure,
center_temperature,
)
reynolds_1 = self.pipe.reynolds_number(flow_1, port_1_temperature)
reynolds_2 = self.pipe.reynolds_number(flow_2, center_temperature)
friction = 0.5 * (
self.pipe.friction_factor(reynolds_1)
+ self.pipe.friction_factor(reynolds_2)
)
return AmesimResults(
times=(0.0,),
variables=(),
saved_variable_indices=(),
series_by_data_path={
"center_p": (center_pressure,),
"center_T": (center_temperature,),
"port_1_p": (port_1_pressure,),
"port_1_T": (port_1_temperature,),
"port_1_dm": (flow_1 / -1.0e-3,),
"port_2_p": (port_2_pressure,),
"port_2_T": (port_2_temperature,),
"port_2_dm": (flow_2 / -1.0e-3,),
"re": (0.5 * (reynolds_1 + reynolds_2),),
"ff": (friction,),
},
final_values_by_data_path={},
)
def test_matching_saved_states_replay_without_integration(self) -> None:
initial_state = self.pipe.get_state_vector()
report = replay_pnl0002_amesim_states(
self.pipe,
self._matching_results(),
self.paths,
)
self.assertEqual(report["mode"], "amesim-state-replay-no-integration")
self.assertEqual(report["pointCount"], 1)
self.assertEqual(self.pipe.get_state_vector(), initial_state)
row = report["rows"][0]
self.assertAlmostEqual(row["port1MassFlowError"], 0.0, places=15)
self.assertAlmostEqual(row["port2MassFlowError"], 0.0, places=15)
self.assertAlmostEqual(row["reynoldsError"], 0.0, places=12)
self.assertAlmostEqual(row["frictionFactorError"], 0.0, places=15)
def test_missing_saved_variable_has_actionable_error(self) -> None:
results = self._matching_results()
results.series_by_data_path.pop("ff")
with self.assertRaisesRegex(
ValueError,
"AMESim replay variable is not saved: ff",
):
replay_pnl0002_amesim_states(self.pipe, results, self.paths)
if __name__ == "__main__":
unittest.main()