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

+22 -216
View File
@@ -23,8 +23,7 @@ from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from app.simulation.performance import performance_span, profile_phase, profile_run
from app.simulation.property_cache import property_cache_run
from app.simulation.solvers.solver import SolverActivityTracker
from app.simulation.config import SolverActivityTracker
from app.system_xml import (
SystemXmlDocument,
SystemXmlValidationReport,
@@ -41,8 +40,15 @@ if TYPE_CHECKING:
@asynccontextmanager
async def _app_lifespan(application: FastAPI) -> AsyncIterator[None]:
import logging
from app.simulation.backends import numeric_engine_name
from app.simulation.warmup import warm_up_simulation_runtime
application.state.simulation_numeric_engine = numeric_engine_name()
logging.getLogger("uvicorn.error").info(
"Simulation numeric engine: %s", application.state.simulation_numeric_engine,
)
application.state.simulation_warmup = warm_up_simulation_runtime().as_dict()
yield
@@ -688,26 +694,15 @@ def run_system_xml_simulation(
cancel_check: Callable[[], bool] | None = None,
activity_tracker: SolverActivityTracker | None = None,
) -> dict[str, object]:
with property_cache_run() as property_cache:
with profile_run() as trace:
result = _run_system_xml_simulation_profiled(
xml_bytes,
progress_callback,
cancel_check,
activity_tracker,
)
with profile_run() as trace:
result = _run_system_xml_simulation_profiled(
xml_bytes,
progress_callback,
cancel_check,
activity_tracker,
)
performance = trace.snapshot()
if performance.get("mode") == "audit" and property_cache is not None:
cache_info = property_cache.info()
performance["propertyCache"] = {
"hits": cache_info.hits,
"misses": cache_info.misses,
"maxEntriesPerCache": cache_info.max_entries_per_cache,
"cacheCount": cache_info.cache_count,
"currentEntries": cache_info.current_entries,
"evictions": cache_info.evictions,
}
if performance.get("mode") != "off":
diagnostics = dict(result.get("diagnostics", {}))
diagnostics["performance"] = performance
@@ -721,13 +716,8 @@ def _run_system_xml_simulation_profiled(
cancel_check: Callable[[], bool] | None = None,
activity_tracker: SolverActivityTracker | None = None,
) -> dict[str, object]:
from app.simulation.solvers.algebraic import AlgebraicSolveError
from app.simulation.solvers.solver import SolveIVPConfig
from app.simulation.solvers.stream import StreamSolveError
from app.simulation.systems.generic import (
GenericFluidSystem,
SimulationPreparationError,
)
from app.simulation.backends import simulate_network
from app.simulation.results import SimulationPreparationError
def emit(
progress: int,
@@ -766,20 +756,9 @@ def _run_system_xml_simulation_profiled(
)
try:
system = GenericFluidSystem(network)
result = system.simulate(
SolveIVPConfig(
t_start=document.simulation.t_start,
t_stop=document.simulation.t_stop,
method=document.simulation.method,
# The pressure-flow closure is solved to a scaled 1e-7
# residual. State-specific mechanical absolute tolerances now
# keep ideal-stop Jacobian perturbations stable, so the outer
# integrator can use its canonical 1e-6 relative accuracy.
rtol=1.0e-6,
max_step=document.simulation.max_step,
),
sample_step=document.simulation.sample_step,
result = simulate_network(
network,
document.simulation,
progress_callback=report_system_progress,
cancel_check=cancel_check,
activity_tracker=activity_tracker,
@@ -799,41 +778,6 @@ def _run_system_xml_simulation_profiled(
],
},
) from exc
except AlgebraicSolveError as exc:
algebraic_diagnostics = exc.diagnostics.as_dict()
algebraic_diagnostics["scopeKind"] = exc.scope_kind
algebraic_diagnostics["scopeComponents"] = list(exc.scope_components)
raise HTTPException(
status_code=422,
detail={
"message": str(exc),
"issues": [
{
"severity": "error",
"layer": "simulation",
"code": "PRESSURE_FLOW_SOLVE_FAILED",
"message": str(exc),
}
],
"diagnostics": algebraic_diagnostics,
},
) from exc
except StreamSolveError as exc:
raise HTTPException(
status_code=422,
detail={
"message": str(exc),
"issues": [
{
"severity": "error",
"layer": "simulation",
"code": "STREAM_SOLVE_FAILED",
"message": str(exc),
}
],
"diagnostics": exc.diagnostics.as_dict(),
},
) from exc
except (RuntimeError, ValueError) as exc:
raise HTTPException(
status_code=422,
@@ -1524,143 +1468,15 @@ def sanitize_project_id(project_id: str) -> str:
def run_reactflow_testmodel(project: ReactFlowProjectPayload) -> dict[str, object]:
from app.simulation.examples.testmodel.run import (
TestModelExecutionConfig,
TestModelRunConfig,
TestModelSamplingConfig,
run_testmodel,
)
from app.simulation.examples.testmodel.system import (
BranchConfig,
CylinderConfig,
OrificeConfig,
PipeConfig,
TankConfig,
TestModelConfig,
)
from app.simulation.solvers.solver import SolveIVPConfig
validate_reactflow_execution_contract(project)
nodes_by_type: dict[str, list[ReactFlowNodePayload]] = {}
for node in project.nodes:
nodes_by_type.setdefault(node.data.modelType, []).append(node)
cylinder = first_node(nodes_by_type, "cylinder")
tank = first_node(nodes_by_type, "tank")
orifices = nodes_by_type.get("orifice", [])
pipes = nodes_by_type.get("pipe", [])
model_config = TestModelConfig(
cylinder=CylinderConfig(
volume=parameter_float(cylinder, "volume", 0.01),
p0=parameter_float(cylinder, "p0", 35e6),
T0=parameter_float(cylinder, "T0", 300.0),
),
upper_branch=BranchConfig(
orifice=OrificeConfig(K=parameter_float(node_at(orifices, 0), "K", 1e-5)),
pipe=pipe_config_from_node(node_at(pipes, 0), PipeConfig),
),
lower_branch=BranchConfig(
orifice=OrificeConfig(K=parameter_float(node_at(orifices, 1), "K", 1e-5)),
pipe=pipe_config_from_node(node_at(pipes, 1), PipeConfig),
),
tank=TankConfig(
volume=parameter_float(tank, "volume", 0.1),
p0=parameter_float(tank, "p0", 1e5),
T0=parameter_float(tank, "T0", 300.0),
),
)
run_config = TestModelRunConfig(
model=model_config,
solver=SolveIVPConfig(
t_start=project.simulation.t_start,
t_stop=project.simulation.t_stop,
method=project.simulation.method,
max_step=project.simulation.max_step,
),
sampling=TestModelSamplingConfig(step=project.simulation.step),
execution=TestModelExecutionConfig(use_modelica_reference_if_available=False),
)
result = run_testmodel(run_config=run_config)
series_keys = ("time", "mytank.p", "mytank.T", "mycylinder.p", "mycylinder.T")
series = {
key: [float(value) for value in result.series[key]]
for key in series_keys
if key in result.series
}
return {
"success": bool(result.solution.success),
"message": str(result.solution.message),
"usedModelicaReference": result.used_modelica_reference,
"final": {
"time": series["time"][-1],
"tankPressure": series["mytank.p"][-1],
"tankTemperature": series["mytank.T"][-1],
"cylinderPressure": series["mycylinder.p"][-1],
"cylinderTemperature": series["mycylinder.T"][-1],
},
"series": series,
"artifacts": {
"primaryCsv": str(result.artifacts.primary_csv_path),
"temperatureCsv": str(result.artifacts.temperature_csv_path),
"temperatureSvg": str(result.artifacts.temperature_svg_path),
"runReport": str(result.artifacts.run_report_path),
},
"networkSummary": result.system.network.summary(),
}
raise HTTPException(status_code=410, detail="The legacy Python example runner has been removed. Export System XML and use /api/system-xml/simulate.")
def run_reactflow_test_mql(project: ReactFlowProjectPayload) -> dict[str, object]:
from app.simulation.examples.test_mql.run import run_test_mql
from app.simulation.examples.test_mql.system import TestMqlRunConfig
validate_reactflow_execution_contract(project)
run_config = TestMqlRunConfig(
t_start=project.simulation.t_start,
t_stop=project.simulation.t_stop,
sample_step=project.simulation.step,
)
result = run_test_mql(run_config=run_config)
series = {
key: [float(value) for value in values]
for key, values in result.result.series.items()
}
final = {key: values[-1] for key, values in series.items() if values}
snapshot = result.system.snapshot()
return {
"success": True,
"message": "test_mql fixed-topology simulation completed.",
"model": {
"name": snapshot.model_name,
"componentCount": snapshot.component_count,
"connectionCount": snapshot.connection_count,
"continuousStateCount": snapshot.continuous_state_count,
"discreteStateCount": snapshot.discrete_state_count,
},
"final": final,
"series": series,
"artifacts": {
"summary": str(result.summary_path),
},
}
raise HTTPException(status_code=410, detail="The legacy Python example runner has been removed. Export System XML and use /api/system-xml/simulate.")
def first_node(
nodes_by_type: dict[str, list[ReactFlowNodePayload]],
model_type: str,
) -> ReactFlowNodePayload | None:
nodes = nodes_by_type.get(model_type, [])
return nodes[0] if nodes else None
def node_at(
nodes: list[ReactFlowNodePayload],
index: int,
) -> ReactFlowNodePayload | None:
return nodes[index] if index < len(nodes) else None
def parameter_float(
@@ -1675,13 +1491,3 @@ def parameter_float(
return float(value)
except (TypeError, ValueError):
raise ValueError(f"Parameter '{name}' on component '{node.id}' must be numeric.")
def pipe_config_from_node(node: ReactFlowNodePayload | None, pipe_config_type):
return pipe_config_type(
length=parameter_float(node, "length", 5.0),
diameter=parameter_float(node, "diameter", 0.02),
lambda_darcy=parameter_float(node, "lambda_darcy", 0.02),
p0=parameter_float(node, "p0", 1e5),
T0=parameter_float(node, "T0", 300.0),
)