优化端口传递物性参数方式,减少上下游元件重复计算量
This commit is contained in:
1 parent
91bd9fb252
commit
5d5a2e1843
12 files changed
+773
-65
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,©,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[]={©.R,©.cp,©.Tref,©.slope,©.mu,©.muT,©.S};
|
||||
if(i==7)copy.real_helium=0;else *fields[i]+=fmax(fabs(*fields[i])*.001,.00001);
|
||||
double expected=native_temperature_ph(©,gas.p,positive_h);
|
||||
size_t count=cache.count;
|
||||
CHECK(native_temperature_ph_context(&cache,©,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()
|
||||
Reference in new issue
Block a user