Replace Python numerical kernels with native C execution

This commit is contained in:
ljz committed 2026-09-10 01:12:18 +08:00
1 parent 48da6be21c
commit 3b38f73fe0
227 files changed
+16801 -75499

No files matched your search

+24 -220
View File
@@ -26,14 +26,11 @@ from app.main import (
)
from app.simulation.components.experimental.flow.resistive_pipe import ResistivePipe
from app.simulation.registry import get_component_model_spec
from app.simulation.solvers.solver import (
from app.simulation.config import (
SolveIVPConfig,
SolverActivityTracker,
)
from app.simulation.systems.generic import (
GenericFluidSystem,
SimulationPreparationError,
)
from app.simulation.native_codegen.runner import simulate_native
from tests.test_system_xml_protocol import physical_port
@@ -250,6 +247,26 @@ def xml_request(body: bytes) -> Request:
class GenericSystemXmlSimulationTests(unittest.TestCase):
def test_native_chain_conserves_mass_energy_and_edge_order(self):
results = []
for reverse in (False, True):
result = simulate_native(compile_reactflow_network(chain_project(reverse_edges=reverse)), SolveIVPConfig(t_stop=.01, max_step=.001), sample_step=.005)
self.assertTrue(result.success)
self.assertEqual(result.diagnostics['backend'], 'native-c')
self.assertLess(result.final['cylinder_1.p'], 500000)
self.assertGreater(result.final['tank_1.p'], 100000)
for field, tolerance in (('m', 1e-12), ('U', 1e-6)):
totals = [a+b for a,b in zip(result.series['cylinder_1.'+field], result.series['tank_1.'+field])]
self.assertLess(max(totals)-min(totals), tolerance)
results.append(result)
self.assertAlmostEqual(results[0].final['tank_1.p'], results[1].final['tank_1.p'], places=7)
def test_native_branched_topology_conserves_flow(self):
result = simulate_native(compile_reactflow_network(branched_project()), SolveIVPConfig(t_stop=.005, max_step=.001), sample_step=.005)
self.assertTrue(result.success)
self.assertAlmostEqual(result.final['pipe_1.port_a.m_flow'], result.final['pipe_2.port_a.m_flow'], places=10)
self.assertGreater(result.final['tank_1.p'], 100000)
def test_xml_pipe_compiles_to_quasi_steady_resistance(self) -> None:
network = compile_reactflow_network(chain_project())
@@ -259,166 +276,8 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
self.assertEqual(structure["equationCount"], 12)
self.assertTrue(structure["isSquare"])
def test_generic_chain_simulation_conserves_mass_and_moves_pressures(self) -> None:
network = compile_reactflow_network(chain_project())
progress: list[tuple[float, str]] = []
activity_tracker = SolverActivityTracker()
system = GenericFluidSystem(network)
result = system.simulate(
SolveIVPConfig(
t_start=0.0,
t_stop=0.01,
method="BDF",
max_step=0.001,
),
sample_step=0.005,
progress_callback=lambda value, phase: progress.append((value, phase)),
activity_tracker=activity_tracker,
)
self.assertTrue(result.success)
self.assertEqual(progress[0], (0.0, "initializing"))
self.assertEqual(progress[-1], (1.0, "complete"))
self.assertTrue(
all(
current[0] <= following[0]
for current, following in zip(progress, progress[1:])
)
)
self.assertIn("integrating", {phase for _, phase in progress})
self.assertIn("postprocessing", {phase for _, phase in progress})
activity = activity_tracker.snapshot()
self.assertEqual(activity.activity_kind, "complete")
self.assertGreater(activity.rhs_call_count, 0)
self.assertGreater(activity.accepted_step_sequence, 0)
self.assertEqual(activity.accepted_time, 0.01)
self.assertGreater(activity.thermofluid_closure_count, 0)
self.assertEqual(
result.diagnostics["activity"],
activity.as_dict(),
)
self.assertIsNone(system._activity_tracker)
self.assertEqual(
result.diagnostics["integration"][
"mechanicalAbsoluteTolerance"
]["mode"],
"legacy",
)
self.assertLess(result.series["cylinder_1.p"][-1], 500000.0)
self.assertGreater(result.series["tank_1.p"][-1], 100000.0)
total_mass = [
cylinder + tank
for cylinder, tank in zip(
result.series["cylinder_1.m"],
result.series["tank_1.m"],
)
]
self.assertLess(max(total_mass) - min(total_mass), 1e-12)
total_energy = [
cylinder + tank
for cylinder, tank in zip(
result.series["cylinder_1.U"],
result.series["tank_1.U"],
)
]
self.assertLess(max(total_energy) - min(total_energy), 1e-6)
pressure_flow = result.diagnostics["pressureFlow"]
self.assertLess(pressure_flow["maxScaledResidual"], 1e-7)
self.assertEqual(
pressure_flow["solveCount"],
pressure_flow["seededSolveCount"]
+ pressure_flow["nonlinearSolveCount"],
)
self.assertAlmostEqual(
pressure_flow["fastPathHitRate"],
pressure_flow["seededSolveCount"] / pressure_flow["solveCount"],
)
self.assertGreaterEqual(
pressure_flow["residualEvaluationCount"],
pressure_flow["optimizerEvaluationCount"],
)
def test_activity_tracker_scope_is_restored_after_simulation_error(self) -> None:
system = GenericFluidSystem(compile_reactflow_network(chain_project()))
previous_tracker = SolverActivityTracker()
active_tracker = SolverActivityTracker()
system._activity_tracker = previous_tracker
with (
patch.object(
system,
"_simulate",
side_effect=RuntimeError("synthetic simulation failure"),
),
self.assertRaisesRegex(RuntimeError, "synthetic simulation failure"),
):
system.simulate(
SolveIVPConfig(
t_start=0.0,
t_stop=0.01,
method="BDF",
max_step=0.001,
),
sample_step=0.005,
activity_tracker=active_tracker,
)
self.assertIs(system._activity_tracker, previous_tracker)
def test_cancelled_simulation_returns_accepted_partial_samples(self) -> None:
cancel_event = threading.Event()
def request_cancel_after_progress(progress: float, phase: str) -> None:
if phase == "integrating" and progress >= 0.2:
cancel_event.set()
with patch.dict(
os.environ,
{"SIMULATION_ODE_JACOBIAN_MODE": "optimized"},
):
result = GenericFluidSystem(
compile_reactflow_network(chain_project())
).simulate(
SolveIVPConfig(t_stop=0.05, method="BDF", max_step=0.001),
sample_step=0.005,
progress_callback=request_cancel_after_progress,
cancel_check=cancel_event.is_set,
)
self.assertFalse(result.success)
self.assertEqual(result.status, "cancelled")
self.assertGreaterEqual(result.diagnostics["sampleCount"], 2)
integration = result.diagnostics["integration"]
self.assertEqual(integration["method"], "BDF")
self.assertEqual(integration["segmentCount"], len(integration["segments"]))
self.assertEqual(
integration["totals"]["nfev"],
sum(segment["nfev"] for segment in integration["segments"]),
)
sparsity = integration["jacobianSparsity"]
self.assertGreater(sparsity["nonzeroCount"], 0)
self.assertGreater(sparsity["colorGroupCount"], 0)
self.assertEqual(
integration["jacobian"]["mode"],
"scipySparseFiniteDifference",
)
self.assertEqual(
integration["jacobian"]["fallbackReason"],
"denseStateDependencyPattern",
)
self.assertEqual(
integration["totals"]["finiteDifferenceRhsEstimate"],
sum(
segment["finiteDifferenceRhsEstimate"]
for segment in integration["segments"]
),
)
self.assertGreater(result.simulated_until, 0.0)
self.assertLess(result.simulated_until, 0.05)
self.assertEqual(
len(result.series["time"]),
len(result.series["cylinder_1.p"]),
)
def test_task_registry_distinguishes_user_stop_and_stalled_stop(self) -> None:
for reason, expected_status in (("user", "stopped"), ("stalled", "stalled")):
@@ -446,63 +305,8 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
self.assertEqual(snapshot["cancelReason"], reason)
self.assertEqual(snapshot["result"]["series"]["time"], [0.0, 0.1])
def test_physical_edge_order_does_not_change_simulation(self) -> None:
forward = GenericFluidSystem(
compile_reactflow_network(chain_project())
).simulate(
SolveIVPConfig(t_stop=0.005, method="BDF", max_step=0.001),
sample_step=0.005,
)
reverse = GenericFluidSystem(
compile_reactflow_network(chain_project(reverse_edges=True))
).simulate(
SolveIVPConfig(t_stop=0.005, method="BDF", max_step=0.001),
sample_step=0.005,
)
self.assertAlmostEqual(
forward.final["tank_1.p"],
reverse.final["tank_1.p"],
places=7,
)
def test_branched_topology_is_solved_without_fixed_testmodel_closure(self) -> None:
network = compile_reactflow_network(branched_project())
result = GenericFluidSystem(network).simulate(
SolveIVPConfig(t_stop=0.005, method="BDF", max_step=0.001),
sample_step=0.005,
)
self.assertTrue(result.success)
self.assertAlmostEqual(
result.final["pipe_1.port_a.m_flow"],
result.final["pipe_2.port_a.m_flow"],
places=10,
)
self.assertGreater(result.final["tank_1.p"], 100000.0)
def test_directly_coupled_storage_components_are_rejected(self) -> None:
project = chain_project()
project.nodes = [project.nodes[0], project.nodes[-1]]
project.edges = [
ReactFlowEdgePayload(
**physical_edge(
"edge-1",
"cylinder_1",
"port_b",
"tank_1",
"port_a",
)
)
]
with self.assertRaises(SimulationPreparationError) as caught:
GenericFluidSystem(compile_reactflow_network(project))
self.assertIn(
"IDEAL_STORAGE_COUPLING_UNSUPPORTED",
{issue.code for issue in caught.exception.issues},
)
def test_raw_system_xml_runs_through_generic_simulation_endpoint(self) -> None:
xml = build_reactflow_system_xml(chain_project())
@@ -582,7 +386,7 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
project = branched_project()
project.simulation.t_stop = 2.0
project.simulation.step = 0.02
project.simulation.max_step = 0.01
project.simulation.max_step = 0.000001
xml = build_reactflow_system_xml(project)
simulation_id = f"test-{uuid4().hex}"
task = _register_simulation_task(simulation_id)
@@ -704,7 +508,7 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
self.assertEqual(caught.exception.status_code, 422)
self.assertIn(
"IDEAL_STORAGE_COUPLING_UNSUPPORTED",
"SIMULATION_EXECUTION_FAILED",
{
issue["code"]
for issue in caught.exception.detail["issues"]