优化端口传递物性参数方式,减少上下游元件重复计算量

This commit is contained in:
ljz committed 2026-09-11 14:42:19 +08:00
1 parent 91bd9fb252
commit 5d5a2e1843
12 files changed
+773 -65

No files matched your search

+7 -5
View File
@@ -12,11 +12,13 @@ from tests.native_reference import reference_data, reference_network
def has_revised_pipe_law(case):
"""The frozen Python flow laws predate the September 2026 corrections."""
return any(c['type'] in ('amesim_pnl0001', 'amesim_pnl0003') or
(c['type'].startswith('amesim_pnl') and
c['medium']['type'] == 'AmesimHeliumPengRobinsonMedium')
for c in case['components'])
"""The frozen flow values include capped pipe iterates, not checked roots.
All PNL media now share the residual-checked scalar solver. Keep the frozen
thermodynamic oracle; the pipe suite checks the actual resistance equation
against independent bisection as well as system-level Amesim curves.
"""
return any(c['type'].startswith('amesim_pnl') for c in case['components'])
class Circuit:
+49
View File
@@ -68,6 +68,55 @@ int main(void) {
run=subprocess.run([str(exe)],capture_output=True,text=True,timeout=15)
self.assertEqual(run.returncode,0,run.stderr)
def test_scalar_pipe_solver_checks_residual_and_bracket_fallback(self):
harness = r'''
#include <stdio.h>
#define CHECK(x) do { if(!(x)){fprintf(stderr,"line %d\n",__LINE__);return 1;} } while(0)
static double reference(double K,double rr) {
double lo=0,hi=fmax(sqrt(K/.02),1);
while(hi*hi*pipe_friction(hi,rr)<K)hi*=2;
for(int i=0;i<100;i++) {
double mid=.5*(lo+hi);
if(mid*mid*pipe_friction(mid,rr)>K)hi=mid;else lo=mid;
}
return .5*(lo+hi);
}
int main(void) {
NativePipeSolve s;int fallbacks=0;
for(int i=0;i<=150;i++)for(int r=0;r<6;r++)for(int scale=0;scale<3;scale++) {
double re=pow(10,-6+.1*i),rr=r==0?0:pow(10,-6+r);
double K=re*re*pipe_friction(re,rr),q_per_re=pow(10,-12+6*scale);
double actual=native_pipe_resistance(K,rr,q_per_re,&s),expected=reference(K,rr);
CHECK(s.converged && isfinite(actual) && s.relative_residual<=1e-9);
CHECK(s.iterations<20 && fabs(actual-expected)<=1e-12+expected*1e-9);
CHECK(fabs(actual*actual*pipe_friction(actual,rr)/K-1)<=1e-9);
fallbacks+=s.bisections;
}
CHECK(fallbacks>0);
CHECK(native_pipe_resistance(0,0,1,&s)==0 && s.converged);
CHECK(isnan(native_pipe_resistance(-1,0,1,&s)) && !s.converged);
CHECK(isnan(native_pipe_resistance(NAN,0,1,&s)) && !s.converged);
CHECK(isnan(native_pipe_resistance(1,0,0,&s)) && !s.converged);
/* Direct laminar handling preserves the distinct PNL00R branch. */
NativeMedium medium={0,287,1005,300,0,1.8e-5,300,110.4};
double p=2e5,T=300,d=.01,L=1,cm,v;
medium_valve(NULL,&medium,p,p-1,T,&cm,&v);
double expected=pow(PI*d*d/4*p*cm,2)/(16*PI*native_viscosity(&medium,T,0)*L*T);
CHECK(4*expected/(PI*d*native_viscosity(&medium,T,0))<1000);
CHECK(native_pipe_flow(&medium,p,p-1,T,d,L,1e-5,0)==expected);
return 0;
}
'''
with tempfile.TemporaryDirectory(prefix='native-pipe-root-') as tmp:
directory=Path(tmp);source=directory/'check.c';exe=directory/'check.exe'
source.write_text((ROOT/'native/components/kernels.c').read_text()+harness)
build=subprocess.run([self.compiler,'-std=c11','-O3','-Wall','-Wextra','-Werror',
'-ffp-contract=off','-fno-fast-math','-static-libgcc','-I',str(ROOT/'native/include'),
str(source),'-lm','-o',str(exe)],capture_output=True,text=True,timeout=60)
self.assertEqual(build.returncode,0,build.stderr)
run=subprocess.run([str(exe)],capture_output=True,text=True,timeout=30)
self.assertEqual(run.returncode,0,run.stderr)
def test_pnl0001_uses_upstream_temperature_in_both_directions(self):
for reverse in (False,True):
with self.subTest(reverse=reverse):
+136
View File
@@ -0,0 +1,136 @@
"""Property reuse must preserve state identity, trial isolation and EOS physics."""
from pathlib import Path
import subprocess
import tempfile
import unittest
from app.simulation.native_codegen.build import toolchain
ROOT = Path(__file__).resolve().parents[1]
class NativePropertyTests(unittest.TestCase):
def test_shared_properties_and_invalidation(self):
try:
compiler = toolchain()[0]
except (OSError, RuntimeError, subprocess.SubprocessError) as exc:
self.skipTest(f"Native toolchain unavailable: {exc}")
source = (ROOT / 'native/components/kernels.c').read_text()
for signature, counter in (
('static double temperature_ph(double p,double h) {', 'ph_calls'),
('static double z_factor(double p,double T) {', 'z_calls'),
('double native_viscosity(const NativeMedium *m, double T, int diagnostic) {', 'mu_calls'),
):
self.assertEqual(source.count(signature), 1)
source = source.replace(signature, signature + f'++{counter};')
source = 'static int ph_calls,z_calls,mu_calls;\n' + source
harness = r'''
#include <stdio.h>
#define CHECK(x) do { if(!(x)){fprintf(stderr,"line %d\n",__LINE__);return 1;} } while(0)
static int close_to(double a,double b,double atol,double rtol) {
return isfinite(a) && isfinite(b) && fabs(a-b)<=atol+rtol*fmax(fabs(a),fabs(b));
}
int main(void) {
NativeMedium m=helium_medium;
NativePropertyState states[32],other_states[2];NativePropertyCache cache,other;
native_properties_init(&cache,states,32);native_properties_init(&other,other_states,2);
double y[2];NativeGas gas;
CHECK(native_medium_init(&m,1.5e7,293.15,.01,0,y));
int before=z_calls;
CHECK(native_medium_gas_context(&cache,&m,y[0],y[1],.01,&gas));
CHECK(z_calls==before); /* h=u+p/rho does not solve the cubic again. */
CHECK(gas.h==gas.u+gas.p/gas.rho);
before=ph_calls;
CHECK(native_temperature_ph_context(&cache,&m,gas.p,gas.h)==gas.T && ph_calls==before);
NativeMedium copy=m;
CHECK(native_temperature_ph_context(&cache,&copy,gas.p,gas.h)==gas.T && ph_calls==before);
double q,cm,v;before=z_calls;
CHECK(native_medium_orifice_context(&cache,&m,gas.p,.6*gas.p,gas.h,gas.h,.001,.5,&q,&cm,&v));
CHECK(z_calls==before+1); /* Only the different downstream state needs rho. */
CHECK(native_medium_orifice_context(&cache,&m,gas.p,.6*gas.p,gas.h,gas.h,.001,.5,&q,&cm,&v));
CHECK(z_calls==before+1);
before=mu_calls;
q=native_pipe_flow_context(&cache,&m,gas.p,.6*gas.p,gas.T,.01,1,1e-5,1);
CHECK(isfinite(q) && mu_calls==before+1);
double diag[4];native_pipe_diagnostics_context(&cache,&m,q,gas.p,gas.T,.01,1,1e-5,1,diag);
CHECK(mu_calls==before+1);
/* Changed pressure (even one ULP), mixed enthalpy and every medium field
must force a fresh PH evaluation. Equal h alone is not a state key. */
double pressures[]={nextafter(gas.p,INFINITY),.2*gas.p,gas.p};
for(int i=0;i<3;i++) {
double h=gas.h+(i==2?1000:0),expected=native_temperature_ph(&m,pressures[i],h);
before=ph_calls;
CHECK(native_temperature_ph_context(&cache,&m,pressures[i],h)==expected);
CHECK(ph_calls==before+1);
CHECK(native_temperature_ph_context(&cache,&m,pressures[i],h)==expected && ph_calls==before+1);
}
CHECK(fabs(native_temperature_ph(&m,.2*gas.p,gas.h)-gas.T)>1e-3);
double positive_h=gas.h+2e6;
(void)native_temperature_ph_context(&cache,&m,gas.p,positive_h);
for(int i=0;i<8;i++) {
copy=m;
double *fields[]={&copy.R,&copy.cp,&copy.Tref,&copy.slope,&copy.mu,&copy.muT,&copy.S};
if(i==7)copy.real_helium=0;else *fields[i]+=fmax(fabs(*fields[i])*.001,.00001);
double expected=native_temperature_ph(&copy,gas.p,positive_h);
size_t count=cache.count;
CHECK(native_temperature_ph_context(&cache,&copy,gas.p,positive_h)==expected);
CHECK(cache.count>count);
}
/* Separate contexts, a new trial, and exhausted capacity remain correct. */
before=ph_calls;
CHECK(isfinite(native_temperature_ph_context(&other,&m,gas.p,gas.h)) && ph_calls==before+1);
native_properties_init(&cache,states,1);
CHECK(native_medium_gas_context(&cache,&m,y[0],y[1],.01,&gas));
for(int i=1;i<30;i++) {
double p=gas.p*(1+i*.01),h=gas.h+i;
CHECK(native_temperature_ph_context(&cache,&m,p,h)==native_temperature_ph(&m,p,h));
CHECK(cache.count==1);
}
native_properties_init(&cache,states,32);before=ph_calls;
CHECK(isfinite(native_temperature_ph_context(&cache,&m,gas.p,gas.h)) && ph_calls==before+1);
size_t count=cache.count;
(void)native_temperature_ph_context(&cache,&m,NAN,gas.h);
CHECK(cache.count==count);
/* Thermodynamic round trips use an independent PT enthalpy formula.
Ideal gases include temperature-dependent heat capacity. */
for(int ideal=0;ideal<2;ideal++) {
NativeMedium fluid=ideal?(NativeMedium){0,287,1005,300,.2,1.8e-5,300,110.4}:m;
for(double T=100;T<=1000;T+=100)for(double p=1e4;p<=1e8;p*=10) {
native_properties_init(&cache,states,32);
CHECK(native_medium_init(&fluid,p,T,.01,0,y));
CHECK(native_medium_gas_context(&cache,&fluid,y[0],y[1],.01,&gas));
double ref_h=ideal?fluid.cp*T+.5*fluid.slope*pow(T-fluid.Tref,2):h_ideal(T)+h_departure(p,T);
CHECK(close_to(gas.h,ref_h,2e-5,1e-9));
CHECK(close_to(gas.p,p,.001,1e-9));
CHECK(close_to(gas.T,T,1e-7,1e-9));
CHECK(close_to(native_temperature_ph(&fluid,gas.p,gas.h),gas.T,1e-7,1e-9));
double oldq,oldcm,oldv;
CHECK(native_medium_orifice(&fluid,gas.p,.8*gas.p,gas.h,gas.h,.001,.5,&oldq,&oldcm,&oldv));
CHECK(native_medium_orifice_context(&cache,&fluid,gas.p,.8*gas.p,gas.h,gas.h,.001,.5,&q,&cm,&v));
CHECK(close_to(q,oldq,1e-10,2e-9) && close_to(v,oldv,1e-7,2e-9));
}
}
/* Subcritical helium retains the pre-existing vapor-root selection. */
native_properties_init(&cache,states,32);
CHECK(native_gas_init(1e4,3,.01,y));
CHECK(native_medium_gas_context(&cache,&m,y[0],y[1],.01,&gas));
CHECK(gas.T<TC && cache.count==0);
CHECK(gas.h==h_ideal(gas.T)+h_departure(gas.p,gas.T));
return 0;
}
'''
with tempfile.TemporaryDirectory(prefix='native-properties-') as tmp:
folder=Path(tmp);c_file=folder/'properties.c';exe=folder/'properties.exe'
c_file.write_text(source+harness)
compiled=subprocess.run([compiler,'-std=c11','-O3','-Wall','-Wextra','-Werror',
'-ffp-contract=off','-fno-fast-math','-static-libgcc','-I',str(ROOT/'native/include'),
str(c_file),'-lm','-o',str(exe)],capture_output=True,text=True,timeout=60)
self.assertEqual(compiled.returncode,0,compiled.stderr)
run=subprocess.run([str(exe)],capture_output=True,text=True,timeout=30)
self.assertEqual(run.returncode,0,run.stderr)
if __name__ == '__main__':
unittest.main()