验收四路模型并优化拓扑求解性能
This commit is contained in:
1 parent
caca32a513
commit
456c29b3b6
20 files changed
+1085
-137
No files matched your search
@@ -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"):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user