前端进度条性能优化、仿真结束后后处理优化;后端C代码生成流程优化:先识别来源,再按照已知未知量需求排序,最后局部求解

This commit is contained in:
ljz committed 2026-09-11 11:27:54 +08:00
1 parent 0dcb465d84
commit 91bd9fb252
77 files changed
+11223 -551

No files matched your search

+90 -52
View File
@@ -6,10 +6,13 @@ All thermodynamics, flow/stream closure and derivatives execute in the EXE.
from __future__ import annotations
import json
import re
from importlib import import_module
from .compiler import NativeCapabilityError, NativeProgram, _Groups, _number as num
from .contracts import SUPPORTED_VERSIONS
from .schedule import Computation, EvaluationSchedule, references
from .tolerances import state_absolute_tolerance
GAS_TYPES = {'amesim_pnch023', 'amesim_pnch012', 'amesim_pnl0001',
@@ -30,7 +33,7 @@ def catalog_contracts():
return result
def linear_schedule(equations, unknowns, free_flows=()):
def linear_assignments(equations, unknowns, free_flows=()):
"""Eliminate constant coefficients; retain RHS expressions as C temporaries.
Exact pivot elimination avoids a numeric pseudoinverse and its tiny spurious
@@ -68,15 +71,21 @@ def linear_schedule(equations, unknowns, free_flows=()):
else:
target[k] = value
pivots.append(key)
lines = [f'double b{i} = {expr};' for i, (_, expr) in enumerate(equations)
lines = [(f'b{i}', expr) for i, (_, expr) in enumerate(equations)
if any(i in rows[j][1] for j in range(len(pivots)))]
for j, key in enumerate(pivots):
expr = ' + '.join(f'({num(v)})*b{i}' for i, v in rows[j][1].items()) or '0.0'
lines.append(f'{key} = {expr};')
lines.append((key, expr))
return lines
def linear_schedule(equations, unknowns, free_flows=()):
return [f'{"double " if key.startswith("b") else ""}{key} = {expr};'
for key, expr in linear_assignments(equations, unknowns, free_flows)]
def compile_extended_program(network):
network.validate_port_supplies()
components = list(network.components.values())
contracts = catalog_contracts()
for c in components:
@@ -297,17 +306,19 @@ def compile_extended_program(network):
coupled.append((root,offsets,volumes))
for root, gas in anchor.items():
lines.append(f'p[{pgi[root]}]={gas}.p;')
default_h = next(iter(gases.values()))+'.h' if gases else '0.0'
for endpoint, idx in pi.items():
lines.append(f'h[{idx}]={port_gas.get(endpoint,default_h.removesuffix(".h"))}.h;' if endpoint in port_gas else f'h[{idx}]={default_h};')
for c in components:
if c.model_type in {'amesim_pnpl01','amesim_pnrp17'}:
# Closed-end ports retain their declared zero outflow enthalpy.
lines.append(f'{h(c,"port_1")}=0.0;')
h_initial = {f'h[{pi[endpoint]}]': gas+'.h' for endpoint,gas in port_gas.items()}
flow_lines, flow_known, flow_eq = [], set(), []
operations, flow_known, flow_eq = [], set(), []
pipe_cache_count = 0
def pipe_flow(expression):
nonlocal pipe_cache_count
result = f'native_pipe_flow_cached(&pipe_cache[{pipe_cache_count}],{expression})'
pipe_cache_count += 1
return result
def flow(c, name, expr):
target=q(c,name);flow_lines.append(f'{target}={expr};');flow_known.add(target)
target=q(c,name)
operations.append(Computation.assignment(f'flow:{c.name}.{name}',target,expr))
flow_known.add(target)
def flow_equation(terms):
flow_eq.append(({q(c,name):coef for c,name,coef in terms},'0.0'))
for c in components:
@@ -331,23 +342,26 @@ def compile_extended_program(network):
flow(c,a,f'copysign(sqrt(fabs({pa}-{pb})*2*fmax(native_density({medium(c)},fmax(.5*({pa}+{pb}),1),{num(c.T0)}),1e-12)*{num(c.area*c.area/resistance)}),{pa}-{pb})')
elif kind=='amesim_pnl00r':
T=f'native_temperature_ph({medium(c)},fmax(fmax({pa},{pb}),1),{pa}>={pb}?{hin(c,a,True)}:{hin(c,b,True)})'
flow(c,a,f'native_pipe_flow({medium(c)},{pa},{pb},{T},{num(c.diam)},{num(c.le)},{num(c.rr)},0)')
flow(c,a,pipe_flow(f'{medium(c)},{pa},{pb},{T},{num(c.diam)},{num(c.le)},{num(c.rr)},0'))
else:
opening = w(c,'xv') if kind!='amesim_pnor001' else '1.0'
if kind!='amesim_pnor001':
value = f'fmax(0,fmin(1,{w(c,"res.signal")}))' if kind=='amesim_pnvo001' else num(c.opening)
put(c,'xv',value)
area = c.effective_cq*(c.effective_area if kind=='amesim_pnor001' else c.maximum_area)
flow_lines.append(f'if(!native_medium_orifice({medium(c)},{pa},{pb},{hin(c,a)},{hin(c,b)},{num(area)},{opening},&{q(c,a)},&{w(c,"cm")},&{w(c,"gasvel")})) return 0;')
inputs=f'{medium(c)},{pa},{pb},{hin(c,a)},{hin(c,b)},{num(area)},{opening}'
outputs=(q(c,a),w(c,'cm'),w(c,'gasvel'))
code=f'if(!native_medium_orifice({inputs},&{outputs[0]},&{outputs[1]},&{outputs[2]})) return 0;'
operations.append(Computation(f'flow:{c.name}.{a}',outputs,references(inputs),(code,)))
flow_known.add(q(c,a));assigned.update((c.name+'.cm',c.name+'.gasvel'))
flow(c,b,f'-{q(c,a)}')
elif kind in ('amesim_pnl0001','amesim_pnl0002'):
gas=gases[c.name,0]
for name in (['port_1'] if kind.endswith('1') else names):
T=gas+'.T'
if kind.endswith('2'):
T=f'({p(c,name)}>{gas}.p?native_temperature_ph({medium(c)},fmax({p(c,name)},1),{hin(c,name,True)}):{gas}.T)'
flow(c,name,f'native_pipe_flow({medium(c)},{p(c,name)},{gas}.p,{T},{num(c.diam)},{num(c.le/(2 if kind.endswith("2") else 1))},{num(c.rr)},1)')
# Resistance uses the gas arriving from the upstream side,
# including inflow into a PNL0001 storage volume.
T=f'({p(c,name)}>{gas}.p?native_temperature_ph({medium(c)},fmax({p(c,name)},1),{hin(c,name,True)}):{gas}.T)'
flow(c,name,pipe_flow(f'{medium(c)},{p(c,name)},{gas}.p,{T},{num(c.diam)},{num(c.le/(2 if kind.endswith("2") else 1))},{num(c.rr)},1'))
for edge in network.connections:
if edge.domain=='pneumatic':
flow_eq.append(({f'q[{pi[e.key]}]':1 for e in edge.endpoints},'0.0'))
@@ -357,46 +371,41 @@ def compile_extended_program(network):
known=''.join(f'-({num(v)})*{k}' for k,v in terms.items() if k in flow_known)
reduced.append(({k:v for k,v in terms.items() if k not in flow_known},rhs+known))
free_flows={f'q[{pi[e]}]' for root,_,_ in coupled for e in pneu if groups.find(e)==root}
flow_lines += linear_schedule(reduced,unknownq,free_flows)
flow_bindings=linear_assignments(reduced,unknownq,free_flows)
flow_temporary_count=max((int(key[1:])+1 for key,_ in flow_bindings if key.startswith('b')),default=0)
for key,expr in flow_bindings:
target=re.sub(r'\bb(\d+)\b',r'fb[\1]',key)
value=re.sub(r'\bb(\d+)\b',r'fb[\1]',expr)
operations.append(Computation.assignment('connection:'+key,target,value,'linear'))
unknownp=[root for root in pgroups if root not in anchor]
residuals=[]
for root in unknownp:
terms=[f'q[{pi[e]}]' for e in pneu if groups.find(e)==root and f'q[{pi[e]}]' in flow_known]
if not terms:
raise NativeCapabilityError('Unanchored pneumatic pressure group has no constitutive flow relation')
residuals.append(' + '.join(terms))
# A monotone nodal mass-balance solve. This is an algebraic connection
# closure; the integration solver's Jacobian policy is unchanged.
pressure_lines=[]
if unknownp:
if not anchor:
raise NativeCapabilityError('Pneumatic pressure network has no storage pressure anchor')
pressure_lines += ['double plo=INFINITY,phi=0;', *[f'plo=fmin(plo,{g}.p);phi=fmax(phi,{g}.p);' for g in anchor.values()]]
pressure_lines += [f'p[{pgi[root]}]=.5*(plo+phi);' for root in unknownp]
pressure_lines += ['int pressure_ok=0;', 'for(int sweep=0;sweep<256;sweep++) {']
for root,expr in zip(unknownp,residuals):
pressure_lines += ['{ double lo=plo,hi=phi;', 'for(int bisect=0;bisect<48;bisect++) {',f'p[{pgi[root]}]=.5*(lo+hi);',
'if(!model_flows(p,h,g,w,q)) return 0;', f'if(({expr})>0) hi=p[{pgi[root]}];else lo=p[{pgi[root]}];','}}']
pressure_lines += ['if(!model_flows(p,h,g,w,q)) return 0;', 'double residual=0;', *[f'residual=fmax(residual,fabs({expr}));' for expr in residuals],
'if(residual<1e-11) { pressure_ok=1;break; }','}', 'if(!pressure_ok) return 0;']
else:
pressure_lines=['if(!model_flows(p,h,g,w,q)) return 0;']
expr=' + '.join(terms)
operations.append(Computation('pressure:'+str(root),(f'p[{pgi[root]}]',),references(expr),(),'pressure',expr))
stream_lines=[]
for c in components:
names=pnames(c);kind=c.model_type
if kind in {'amesim_pnpl01','amesim_pnrp17'}:
stream_lines.append(f'{h(c,"port_1")}={hin(c,"port_1")};')
operations.append(Computation.assignment(f'alias:{c.name}.port_1',h(c,'port_1'),hin(c,'port_1'),'alias'))
elif kind in RESISTORS:
a,b=names
stream_lines += [f'{h(c,a)}={hin(c,b)};',f'{h(c,b)}={hin(c,a)};']
for name,other in ((a,b),(b,a)):
operations.append(Computation.assignment(f'alias:{c.name}.{name}',h(c,name),hin(c,other),'alias'))
elif kind in NODES:
stream_lines += ['{ double total=0,energy=0,average=0;', *[f'if({q(c,name)}>1e-12) {{total+={q(c,name)};energy+={q(c,name)}*{hin(c,name)};}} average+={hin(c,name)};' for name in names]]
if kind!='tee':
# Reference copies have no flow dependency. Keep them separate
# from the reference port's returned energy/mixing calculation.
for name in names:
if name!='port_2':
operations.append(Computation.assignment(f'alias:{c.name}.{name}',h(c,name),hin(c,'port_2'),'alias'))
stream_lines = ['{ double total=0,energy=0,average=0;', *[f'if({q(c,name)}>1e-12) {{total+={q(c,name)};energy+={q(c,name)}*{hin(c,name)};}} average+={hin(c,name)};' for name in names]]
if kind=='tee':
stream_lines += [f'double mixed=total>1e-12?energy/total:average/{len(names)};', *[f'{h(c,name)}=mixed;' for name in names]]
else:
stream_lines += [f'double ref={hin(c,"port_2")},mixed=total>1e-12?energy/total:ref;', *[f'{h(c,name)}=ref;' for name in names if name!='port_2'],f'{h(c,"port_2")}=mixed;',f'if({q(c,"port_2")}<0) {{ double e=0,scale=0;',
stream_lines += [f'double ref={hin(c,"port_2")},mixed=total>1e-12?energy/total:ref;', f'{h(c,"port_2")}=mixed;',f'if({q(c,"port_2")}<0) {{ double e=0,scale=0;',
*[f'e+={q(c,name)}*({q(c,name)}>1e-12?{hin(c,name)}:ref);scale+=fabs({q(c,name)});' for name in names if name!='port_2'],
f'double flow={q(c,"port_2")},transition=fmax(.05*scale,1e-12);',
'double inv=-flow>=transition?1/flow:flow*(2*transition*transition-flow*flow)/pow(transition,4);',
@@ -405,10 +414,34 @@ def compile_extended_program(network):
if c.name+'.T' in slots:
raise NativeCapabilityError('Unexpected node temperature output contract')
stream_lines += ['}']
if pneu:
lines += ['int closure_ok=0;',f'for(int closure=0;closure<{max(64,4*len(pneu))};closure++) {{',f'double previous[{len(pneu)}];',f'for(int i=0;i<{len(pneu)};i++) previous[i]=h[i];',*pressure_lines,*stream_lines,
'double change=0;',f'for(int i=0;i<{len(pneu)};i++) change=fmax(change,fabs(h[i]-previous[i])/fmax(1,fabs(h[i])));',
'if(change<1e-12) {closure_ok=1;break;}','}', 'if(!closure_ok) return 0;', 'if(!model_flows(p,h,g,w,q)) return 0;']
outputs=tuple(h(c,name) for name in (names if kind=='tee' else ['port_2']))
inputs=frozenset(q(c,name) for name in names)|frozenset(hin(c,name) for name in names)
operations.append(Computation(f'stream:{c.name}',outputs,inputs,tuple(stream_lines),'stream'))
# Current state/parameter/signal values are available before this phase.
# Default h guesses are deliberately absent from the known-source map.
labels={f'w[{i}]': key for key,i in slots.items()}
known={f'w[{slots[key]}]': 'prepared:'+key for key in assigned}
gas_origins={gas: 'state:'+','.join(f'{name}.{field}{str(half) if half else ""}' for field in ('m','U'))
for (name,half),gas in gases.items()}
for gas,origin in gas_origins.items():
for field in ('p','T','rho','u','h'):
known[gas+'.'+field]=origin
for root,gas in anchor.items():
known[f'p[{pgi[root]}]']=gas_origins[gas]
for endpoint,gas in port_gas.items():
known[f'h[{pi[endpoint]}]']=gas_origins[gas]
for endpoint,index in pi.items():
labels[f'h[{index}]']='.'.join(endpoint)+'.h_outflow'
labels[f'q[{index}]']='.'.join(endpoint)+'.m_flow'
for root,index in pgi.items():
labels[f'p[{index}]']='pressure:'+','.join('.'.join(ep) for ep in pneu if groups.find(ep)==root)
schedule=EvaluationSchedule(operations,known,labels)
for target,expr in h_initial.items():
if target not in schedule.producers:
lines.append(f'{target}={expr};')
schedule_helpers, scheduled_lines=schedule.emit()
lines += scheduled_lines
for c in components:
for name in pnames(c):
for field,expr in [('p',p(c,name)),('m_flow',q(c,name)),('h_outflow',h(c,name))]:
@@ -510,7 +543,11 @@ def compile_extended_program(network):
diag=[(flow,f'fmax(fmax({a}.p,{b}.p),1)',f'({flow}>=0?{a}.T:{b}.T)',c.le,1)]
else:
pa,pb=p(c,'port_1'),p(c,'port_2');pp=f'fmax(fmax({pa},{pb}),1)'
temp=gases[c.name,0]+'.T' if kind=='amesim_pnl0001' else f'fmax(native_temperature_ph({medium(c)},{pp},{pa}>={pb}?{hin(c,"port_1",True)}:{hin(c,"port_2",True)}),1)'
if kind=='amesim_pnl0001':
gas=gases[c.name,0]
temp=f'({pa}>{gas}.p?fmax(native_temperature_ph({medium(c)},{pp},{hin(c,"port_1",True)}),1):{gas}.T)'
else:
temp=f'fmax(native_temperature_ph({medium(c)},{pp},{pa}>={pb}?{hin(c,"port_1",True)}:{hin(c,"port_2",True)}),1)'
diag=[(q(c,'port_1'),pp,temp,c.le,0)]
lines.append('{ double d[4],acc[4]={0};')
for flow,pp,temp,length,diagnostic in diag:
@@ -532,15 +569,16 @@ def compile_extended_program(network):
np,ng,nq=max(1,len(pgroups)),max(1,gas_count),max(1,len(pneu))
source='\n'.join(['#include "model.h"','#include <math.h>',*declarations,
f'const NativeStop model_stops[{max(1,len(stops))}] = {{'+(','.join('{'+str(s[0])+','+','.join(num(v) for v in s[1:])+'}' for s in stops) or '{0,0,0,0,0,0,0}')+'};',
'const double model_atol[NSTATES] = {'+','.join('1e-12' if k.endswith(('.v','.x')) else '1e-8' for k in state_keys or ['dummy'])+'};',
'const double model_atol[NSTATES] = {'+','.join(map(state_absolute_tolerance, state_keys or ['dummy']))+'};',
'const char *const model_output_keys[NOUTPUTS] = {'+(','.join(json.dumps(v.key,ensure_ascii=True) for v in variables) or '""')+'};',
'static int model_flows(const double *p,const double *h,const NativeGas *g,double *w,double *q) {',
'(void)p;(void)h;(void)g;(void)w;(void)q;',*flow_lines,'return 1;}',
*schedule_helpers,
'int model_init(double *y) {',*[f'y[{i}]={num(v)};' for i,v in enumerate(initial)],*gas_initializers,'return 1;}',
'int model_eval(double t,const double *y,double *dy,double *w) {',
*(['double projected[NSTATES];for(int i=0;i<NSTATES;i++) projected[i]=y[i];',*project,'y=projected;'] if project else []),
f'double p[{np}]={{0}},h[{nq}]={{0}},q[{nq}]={{0}};NativeGas g[{ng}];',
'(void)t;(void)y;(void)w;(void)p;(void)h;(void)q;(void)g;(void)model_flows;',*lines,
f'double fb[{max(1,flow_temporary_count)}]={{0}};(void)fb;',
*([f'NativePipeCache pipe_cache[{max(1,pipe_cache_count)}]={{0}};(void)pipe_cache;'] if pneu else []),
'(void)t;(void)y;(void)w;(void)p;(void)h;(void)q;(void)g;',*lines,
'for(int i=0;i<NSTATES;i++) if(!isfinite(dy[i])) return 0;',
'for(int i=0;i<NOUTPUTS;i++) if(!isfinite(w[i])) return 0;','return 1;}',
'double model_next_break(double t,double end) { double result=end;(void)t;',*breaks,'return result;}',''])
@@ -558,4 +596,4 @@ int model_eval(double t,const double *y,double *dy,double *w);
double model_next_break(double t,double end);
#endif
'''
return NativeProgram(source,header,tuple(state_keys),variables,tuple(sorted({c.model_type for c in components})))
return NativeProgram(source,header,tuple(state_keys),variables,tuple(sorted({c.model_type for c in components})),schedule.report())