优化雅可比矩阵计算;端口转发情况下仿真结果传输方式优化
This commit is contained in:
1 parent
3bc4be3c06
commit
aa4951b14e
28 files changed
+8038
-23
No files matched your search
@@ -13,6 +13,7 @@ from app.simulation.core.metadata import ResultVariableMetadata
|
||||
from app.simulation.systems.network import SimulationNetwork
|
||||
from .contracts import SUPPORTED_TYPES, SUPPORTED_VERSIONS
|
||||
from .tolerances import state_absolute_tolerance
|
||||
from .jacobian import JacobianStructure
|
||||
|
||||
|
||||
class NativeCapabilityError(ValueError):
|
||||
@@ -35,6 +36,7 @@ class NativeProgram:
|
||||
variables: tuple[ResultVariableMetadata, ...]
|
||||
component_types: tuple[str, ...]
|
||||
evaluation_schedule: dict | None = None
|
||||
jacobian_structure: dict | None = None
|
||||
|
||||
def manifest(self) -> dict:
|
||||
return {
|
||||
@@ -42,7 +44,8 @@ class NativeProgram:
|
||||
"variables": [v.as_dict() for v in self.variables],
|
||||
"componentTypes": self.component_types,
|
||||
"componentVersions": {name: SUPPORTED_VERSIONS[name] for name in self.component_types},
|
||||
"jacobianPolicy": "CVODE default; no custom Jacobian",
|
||||
"jacobianPolicy": (self.jacobian_structure or {}).get("policy", "CVODE default; no custom Jacobian"),
|
||||
"jacobianStructure": self.jacobian_structure,
|
||||
"evaluationSchedule": self.evaluation_schedule or {"strategy": "storage-anchored", "cyclicBlockCount": 0},
|
||||
}
|
||||
|
||||
@@ -362,6 +365,7 @@ def _compile_storage_anchored_program(network: SimulationNetwork) -> NativeProgr
|
||||
next_event.append(f"if (t < {_number(c.time)}) result = fmin(result, {_number(c.time)});")
|
||||
else:
|
||||
next_event.append(f"result = fmin(result, native_signal_break(t, end, {_number(c.tstart)}, {c.nstages}, {int(c.iscyclic)}, signal_{index}));")
|
||||
jacobian = JacobianStructure.dense(len(state_keys), 'Compact storage-anchored lowering retains default differences')
|
||||
source = '\n'.join([
|
||||
'#include "model.h"', '#include <math.h>', *declarations,
|
||||
f"const NativeStop model_stops[{max(1,len(stops))}] = {{{stop_c}}};",
|
||||
@@ -387,9 +391,10 @@ def _compile_storage_anchored_program(network: SimulationNetwork) -> NativeProgr
|
||||
extern const NativeStop model_stops[{max(1,len(stops))}];
|
||||
extern const double model_atol[NSTATES];
|
||||
extern const char *const model_output_keys[NOUTPUTS];
|
||||
{chr(10).join(jacobian.header_lines())}
|
||||
int model_init(double *y);
|
||||
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})),jacobian_structure=jacobian.manifest())
|
||||
@@ -13,6 +13,7 @@ from .compiler import NativeCapabilityError, NativeProgram, _Groups, _number as
|
||||
from .contracts import SUPPORTED_VERSIONS
|
||||
from .schedule import Computation, EvaluationSchedule, references
|
||||
from .tolerances import state_absolute_tolerance
|
||||
from .jacobian import StateDependencies, expression_inputs
|
||||
|
||||
|
||||
GAS_TYPES = {'amesim_pnch023', 'amesim_pnch012', 'amesim_pnl0001',
|
||||
@@ -149,6 +150,7 @@ def compile_extended_program(network):
|
||||
nstates = max(len(state_keys), 1)
|
||||
if not initial:
|
||||
initial = [0.0]
|
||||
dependencies = StateDependencies(len(state_keys))
|
||||
lines, declarations, breaks = [], [], []
|
||||
assigned = set()
|
||||
def w(c, field):
|
||||
@@ -156,6 +158,7 @@ def compile_extended_program(network):
|
||||
return f'w[{slots[name+"."+field]}]'
|
||||
def put(c, field, expr, dest=None):
|
||||
(lines if dest is None else dest).append(f'{w(c,field)} = {expr};')
|
||||
dependencies.expression(w(c,field), expr)
|
||||
assigned.add((c if isinstance(c,str) else c.name)+'.'+field)
|
||||
def y(c, field):
|
||||
return f'y[{states[c.name,field]}]'
|
||||
@@ -268,7 +271,10 @@ def compile_extended_program(network):
|
||||
if kind=='amesim_pnch012' else
|
||||
c.cvol if kind=='amesim_pnch023' else c.V if kind in ('cylinder','tank') else c.volume)
|
||||
gas_initializers.append(f'if(!native_medium_init({medium(c)},{num(p0)},{num(T0)},{num(initial_volume)},{int(kind in ("cylinder","tank"))},&y[{states[c.name,"m"+suffix]}])) return 0;')
|
||||
lines.append(f'if(!native_medium_gas_context(properties,{medium(c)}, {y(c,"m"+suffix)}, {y(c,"U"+suffix)}, {V}, &{gas})) return 0;')
|
||||
lines.append(f'if(!native_medium_gas_context(gas_properties,{medium(c)}, {y(c,"m"+suffix)}, {y(c,"U"+suffix)}, {V}, &{gas})) return 0;')
|
||||
gas_inputs = expression_inputs(f'{y(c,"m"+suffix)}+{y(c,"U"+suffix)}+({V})')
|
||||
for field in ('p','T','rho','u','h'):
|
||||
dependencies.assign(gas+'.'+field, gas_inputs)
|
||||
for field in ('m', 'U'):
|
||||
put(c, field+suffix, y(c, field+suffix))
|
||||
for field in ('p','T','rho','u','h'):
|
||||
@@ -304,8 +310,11 @@ def compile_extended_program(network):
|
||||
project += [f'projected[{i}]=mass*{num(volume/total)};projected[{i+1}]=energy*{num(volume/total)};']
|
||||
project.append('}')
|
||||
coupled.append((root,offsets,volumes))
|
||||
dependencies.project_states(offsets)
|
||||
dependencies.project_states([i+1 for i in offsets])
|
||||
for root, gas in anchor.items():
|
||||
lines.append(f'p[{pgi[root]}]={gas}.p;')
|
||||
dependencies.assign(f'p[{pgi[root]}]', (gas+'.p',))
|
||||
h_initial = {f'h[{pi[endpoint]}]': gas+'.h' for endpoint,gas in port_gas.items()}
|
||||
|
||||
operations, flow_known, flow_eq = [], set(), []
|
||||
@@ -437,9 +446,12 @@ def compile_extended_program(network):
|
||||
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 operation in operations:
|
||||
dependencies.computation(operation)
|
||||
for target,expr in h_initial.items():
|
||||
if target not in schedule.producers:
|
||||
lines.append(f'{target}={expr};')
|
||||
dependencies.expression(target, expr)
|
||||
schedule_helpers, scheduled_lines=schedule.emit()
|
||||
lines += scheduled_lines
|
||||
for c in components:
|
||||
@@ -481,7 +493,10 @@ def compile_extended_program(network):
|
||||
if c.use_friction: extra+=f'-{num(c.wind)}*{v}*fabs({v})'
|
||||
feq.append(({w(c,'port_1.f'):1,w(c,'port_2.f'):1,w(ref,'a'):-c.mass},f'-({extra})'))
|
||||
unknownf=[w(c,name+'.f') for c in components for name in mnames(c)]+[w(group[0],'a') for group in mass_groups.values()]
|
||||
lines += linear_schedule(feq,unknownf)
|
||||
# Keep the same elimination/order while registering its structured bindings.
|
||||
for target, expression in linear_assignments(feq, unknownf):
|
||||
lines.append(f'{"double " if target.startswith("b") else ""}{target} = {expression};')
|
||||
dependencies.expression(target, expression)
|
||||
for c in components:
|
||||
for name in mnames(c): assigned.add(c.name+'.'+name+'.f')
|
||||
if c.model_type=='amesim_lmechn1':
|
||||
@@ -491,6 +506,8 @@ def compile_extended_program(network):
|
||||
ref=group[0];vi=states[ref.name,'v'];xi=states[ref.name,'x']
|
||||
limits=[c for c in group if int(c.stoptype) in (1,3)]
|
||||
lines += [f'dy[{vi}]={w(ref,"a")};dy[{xi}]={y(ref,"v")};']
|
||||
dependencies.expression(f'dy[{vi}]', w(ref,'a'))
|
||||
dependencies.expression(f'dy[{xi}]', y(ref,'v'))
|
||||
if limits:
|
||||
lower=max(c.xmin for c in limits);upper=min(c.xmax for c in limits)
|
||||
if lower>upper or ref.x0<lower-1e-12 or ref.x0>upper+1e-12:
|
||||
@@ -502,6 +519,7 @@ def compile_extended_program(network):
|
||||
thresholds.append(max((c.restdvel for c in active if int(c.stoptype)==3),default=0))
|
||||
stops.append((vi,lower,upper,*restitution,*thresholds))
|
||||
lines.append(f'native_stop_motion({y(ref,"x")},{y(ref,"v")},{num(lower)},{num(upper)},&dy[{vi}],&dy[{xi}]);')
|
||||
dependencies.stop_motion(vi, xi)
|
||||
for c in group: put(c,'a',f'dy[{vi}]')
|
||||
|
||||
for c in components:
|
||||
@@ -530,6 +548,8 @@ def compile_extended_program(network):
|
||||
temp=f'.5*({gases[c.name,1]}.T+{gases[c.name,2]}.T)' if half else gas+'.T'
|
||||
heat=f'{num(c.kth*c.exchange_area/(2 if half else 1))}*({num(c.extemp)}-({temp}))'
|
||||
lines += [f'dy[{states[c.name,"m"+suffix]}]={mass};',f'dy[{states[c.name,"U"+suffix]}]={energy}+({heat});']
|
||||
dependencies.expression(f'dy[{states[c.name,"m"+suffix]}]', mass)
|
||||
dependencies.expression(f'dy[{states[c.name,"U"+suffix]}]', f'{energy}+({heat})')
|
||||
if kind.startswith('amesim_pnl'):
|
||||
diag=[]
|
||||
if kind=='amesim_pnl0002':
|
||||
@@ -562,21 +582,26 @@ def compile_extended_program(network):
|
||||
lines.append('{ double mass='+ '+'.join(f'dy[{i}]' for i in offsets)+',energy='+ '+'.join(f'dy[{i+1}]' for i in offsets)+';')
|
||||
for i,volume in zip(offsets,volumes):
|
||||
lines.append(f'dy[{i}]=mass*{num(volume/sum(volumes))};dy[{i+1}]=energy*{num(volume/sum(volumes))};')
|
||||
dependencies.assign(f'dy[{i}]', (f'dy[{j}]' for j in offsets))
|
||||
dependencies.assign(f'dy[{i+1}]', (f'dy[{j+1}]' for j in offsets))
|
||||
lines.append('}')
|
||||
missing=set(slots)-assigned
|
||||
if missing: raise NativeCapabilityError(f'Native output mapping incomplete: {sorted(missing)}')
|
||||
if not state_keys: lines.append('dy[0]=0;')
|
||||
jacobian = dependencies.build()
|
||||
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(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 '""')+'};',
|
||||
*jacobian.source_lines(),
|
||||
*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) {',
|
||||
'static int model_eval_internal(double t,const double *y,double *dy,double *w,int canonical) {',
|
||||
f'NativePropertyState property_states[{min(256,max(16,4*gas_count+2*len(components)))}];',
|
||||
'NativePropertyCache property_cache, *properties=&property_cache;',
|
||||
'native_properties_init(properties,property_states,sizeof(property_states)/sizeof(property_states[0]));',
|
||||
'NativePropertyCache *gas_properties=canonical?NULL:properties;(void)gas_properties;',
|
||||
*(['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}];',
|
||||
f'double fb[{max(1,flow_temporary_count)}]={{0}};(void)fb;',
|
||||
@@ -584,6 +609,8 @@ def compile_extended_program(network):
|
||||
'(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;}',
|
||||
'int model_eval(double t,const double *y,double *dy,double *w) {return model_eval_internal(t,y,dy,w,0);}',
|
||||
'int model_eval_jacobian(double t,const double *y,double *dy,double *w) {return model_eval_internal(t,y,dy,w,1);}',
|
||||
'double model_next_break(double t,double end) { double result=end;(void)t;',*breaks,'return result;}',''])
|
||||
header=f'''#ifndef GENERATED_NATIVE_MODEL_H
|
||||
#define GENERATED_NATIVE_MODEL_H
|
||||
@@ -594,9 +621,10 @@ def compile_extended_program(network):
|
||||
extern const NativeStop model_stops[{max(1,len(stops))}];
|
||||
extern const double model_atol[NSTATES];
|
||||
extern const char *const model_output_keys[NOUTPUTS];
|
||||
{chr(10).join(jacobian.header_lines(canonical_rhs=True))}
|
||||
int model_init(double *y);
|
||||
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})),schedule.report())
|
||||
return NativeProgram(source,header,tuple(state_keys),variables,tuple(sorted({c.model_type for c in components})),schedule.report(),jacobian.manifest(canonical_rhs=True))
|
||||
@@ -0,0 +1,214 @@
|
||||
"""Conservative state dependencies for compiler-owned expressions.
|
||||
|
||||
This is structural bookkeeping, not C parsing or numerical Jacobian evaluation.
|
||||
Native multi-output operations, projections and mode-dependent writes must be
|
||||
registered explicitly by the lowering path. Unresolved reachable inputs disable
|
||||
coloring for the complete model.
|
||||
"""
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
# Reviewed expression leaves. Unknown arrays/scalars remain unresolved instead
|
||||
# of silently becoming constants; the flow schedule supplies its own explicit IR.
|
||||
_ARRAY = re.compile(r'\b[A-Za-z_]\w*\[\d+\](?:\.[A-Za-z_]\w*)?|\bgas_\d+\.[A-Za-z_]\w*')
|
||||
_IDENTIFIER = re.compile(r'\b[A-Za-z_]\w*\b')
|
||||
_NUMBER = re.compile(r'(?<![\w.])(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?')
|
||||
_FUNCTIONS = frozenset({
|
||||
'fmax', 'fmin', 'fabs', 'sqrt', 'copysign', 'pow',
|
||||
'native_signal', 'native_contact', 'native_limit_force',
|
||||
'native_pipe_flow_context', 'native_pipe_flow_cached_context',
|
||||
'native_temperature_ph_context', 'native_density_context',
|
||||
})
|
||||
_CONSTANTS = frozenset({'t', 'properties', 'NAN', 'INFINITY', 'NULL', 'true', 'false'})
|
||||
|
||||
|
||||
def expression_inputs(expression):
|
||||
"""Extract leaves from a reviewed expression; unfamiliar names fail closed."""
|
||||
inputs = set()
|
||||
def leaf(match):
|
||||
key = match.group()
|
||||
# Per-evaluation cache scratch is an implementation detail of reviewed
|
||||
# kernels; each value is determined by that kernel's explicit arguments.
|
||||
if not re.fullmatch(r'pipe_cache\[\d+\]', key):
|
||||
inputs.add(key)
|
||||
return ' '
|
||||
remainder = _ARRAY.sub(leaf, expression)
|
||||
remainder = _NUMBER.sub(' ', remainder)
|
||||
for name in _IDENTIFIER.findall(remainder):
|
||||
if name in _FUNCTIONS or name in _CONSTANTS or re.fullmatch(r'(?:medium|signal)_\d+', name):
|
||||
continue
|
||||
inputs.add(name)
|
||||
return frozenset(inputs)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JacobianStructure:
|
||||
state_count: int
|
||||
rows: tuple[tuple[int, ...], ...] = ()
|
||||
colors: tuple[int, ...] = ()
|
||||
reason: str | None = None
|
||||
|
||||
@classmethod
|
||||
def dense(cls, state_count, reason):
|
||||
return cls(state_count, reason=reason)
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.reason is None
|
||||
|
||||
@property
|
||||
def color_count(self):
|
||||
return max(self.colors, default=-1) + 1 if self.enabled else self.state_count
|
||||
|
||||
@property
|
||||
def nonzeros(self):
|
||||
return sum(map(len, self.rows)) if self.enabled else self.state_count ** 2
|
||||
|
||||
def manifest(self, *, canonical_rhs=False):
|
||||
# Use proven coloring automatically when it reduces finite-difference
|
||||
# groups; unsupported or unhelpful patterns retain dense differences.
|
||||
runtime_eligible = self.enabled and 0 < self.color_count < self.state_count
|
||||
fallback_reason = None if runtime_eligible else (
|
||||
self.reason or 'Coloring does not reduce finite-difference groups')
|
||||
pattern = {'rows': self.rows, 'columnColors': self.colors}
|
||||
policy = ('CVODE colored forward differences; canonical-property-cache RHS' if canonical_rhs else 'CVODE colored forward differences') if runtime_eligible else 'CVODE default dense differences'
|
||||
return {
|
||||
'policyScope': 'default-runtime',
|
||||
'defaultRuntimePolicy': policy,
|
||||
'verification': '--verify-jacobian',
|
||||
'policy': policy,
|
||||
'rhsPolicy': 'canonical-property-cache finite differences' if canonical_rhs and runtime_eligible else 'ordinary model_eval',
|
||||
'runtimeEligible': runtime_eligible, 'runtimeFallbackReason': fallback_reason,
|
||||
'canonicalRhs': canonical_rhs, 'ordinaryRhsUnchanged': True,
|
||||
'enabled': self.enabled, 'reason': self.reason, 'stateCount': self.state_count,
|
||||
'nonzeros': self.nonzeros, 'density': self.nonzeros / self.state_count ** 2 if self.state_count else 0,
|
||||
'colorCount': self.color_count,
|
||||
'patternSha256': hashlib.sha256(json.dumps(pattern, separators=(',', ':')).encode()).hexdigest() if self.enabled else None,
|
||||
'columnColors': list(self.colors) if self.enabled else None,
|
||||
}
|
||||
|
||||
def header_lines(self, *, canonical_rhs=False):
|
||||
lines = [f'#define MODEL_JACOBIAN_COLORED {int(self.enabled)}',
|
||||
f'#define MODEL_JACOBIAN_CANONICAL_RHS {int(canonical_rhs)}',
|
||||
f'#define MODEL_JACOBIAN_COLOR_COUNT {self.color_count}',
|
||||
f'#define MODEL_JACOBIAN_NNZ {self.nonzeros}']
|
||||
if canonical_rhs:
|
||||
lines += ['int model_eval_jacobian(double t, const double *y, double *dy, double *w);']
|
||||
if self.enabled:
|
||||
lines += ['extern const int model_jacobian_column_color[NSTATES];',
|
||||
'extern const int model_jacobian_col_ptr[NSTATES+1];',
|
||||
'extern const int model_jacobian_row_index[MODEL_JACOBIAN_NNZ];']
|
||||
return lines
|
||||
|
||||
def source_lines(self):
|
||||
if not self.enabled:
|
||||
return []
|
||||
columns = [[] for _ in range(self.state_count)]
|
||||
for row, entries in enumerate(self.rows):
|
||||
for column in entries:
|
||||
columns[column].append(row)
|
||||
pointers = [0]
|
||||
indices = []
|
||||
for column in columns:
|
||||
indices.extend(column)
|
||||
pointers.append(len(indices))
|
||||
return [f'const int {name}[{len(values)}] = {{'+','.join(map(str, values))+'};'
|
||||
for name, values in [('model_jacobian_column_color', self.colors),
|
||||
('model_jacobian_col_ptr', pointers),
|
||||
('model_jacobian_row_index', indices)]]
|
||||
|
||||
|
||||
class StateDependencies:
|
||||
def __init__(self, state_count):
|
||||
self.state_count = state_count
|
||||
self.seeds = {f'y[{i}]': 1 << i for i in range(state_count)}
|
||||
self.inputs = {}
|
||||
|
||||
def assign(self, target, inputs):
|
||||
# Union repeated writes, rather than dropping dependencies from another
|
||||
# branch or an earlier in-place value (stop motion is mode dependent).
|
||||
self.inputs.setdefault(target, set()).update(inputs)
|
||||
|
||||
def expression(self, target, expression):
|
||||
self.assign(target, expression_inputs(expression))
|
||||
|
||||
def project_states(self, offsets):
|
||||
refs = {f'y[{i}]' for i in offsets}
|
||||
for target in refs:
|
||||
self.assign(target, refs)
|
||||
|
||||
def stop_motion(self, velocity_index, position_index):
|
||||
refs = {f'y[{velocity_index}]', f'y[{position_index}]',
|
||||
f'dy[{velocity_index}]', f'dy[{position_index}]'}
|
||||
for target in (f'dy[{velocity_index}]', f'dy[{position_index}]'):
|
||||
self.assign(target, refs)
|
||||
|
||||
def computation(self, operation):
|
||||
# A schedule SCC reaches a fixed point in build(), so every member gains
|
||||
# every external state dependency even through pressure/stream loops.
|
||||
for output in operation.outputs:
|
||||
self.assign(output, operation.inputs)
|
||||
|
||||
def build(self):
|
||||
if self.state_count == 0:
|
||||
return JacobianStructure.dense(1, 'Algebraic-only internal state uses default differences')
|
||||
required = {f'dy[{i}]' for i in range(self.state_count)}
|
||||
pending = list(required)
|
||||
reachable = set()
|
||||
missing = set()
|
||||
while pending:
|
||||
key = pending.pop()
|
||||
if key in reachable:
|
||||
continue
|
||||
reachable.add(key)
|
||||
if key not in self.inputs and key not in self.seeds:
|
||||
missing.add(key)
|
||||
pending.extend(self.inputs.get(key, ()))
|
||||
if missing:
|
||||
return JacobianStructure.dense(self.state_count,
|
||||
'Unresolved structural inputs: '+', '.join(sorted(missing)[:16]))
|
||||
consumers = {key: set() for key in reachable}
|
||||
for target in reachable:
|
||||
for source in self.inputs.get(target, ()):
|
||||
consumers[source].add(target)
|
||||
masks = dict(self.seeds)
|
||||
queue = deque(key for key in self.seeds if key in reachable)
|
||||
queued = set(queue)
|
||||
while queue:
|
||||
source = queue.popleft()
|
||||
queued.remove(source)
|
||||
for target in consumers[source]:
|
||||
merged = masks.get(target, 0) | masks[source]
|
||||
if merged != masks.get(target, 0):
|
||||
masks[target] = merged
|
||||
if target not in queued:
|
||||
queue.append(target)
|
||||
queued.add(target)
|
||||
# A diagonal overestimate is safe and keeps isolated/dummy rows valid
|
||||
# without introducing zero-length C arrays or uncolored state columns.
|
||||
rows = tuple(tuple(column for column in range(self.state_count)
|
||||
if (masks.get(f'dy[{row}]', 0) | (1 << row)) >> column & 1)
|
||||
for row in range(self.state_count))
|
||||
conflicts = [set() for _ in range(self.state_count)]
|
||||
for entries in rows:
|
||||
for column in entries:
|
||||
conflicts[column].update(set(entries) - {column})
|
||||
colors = {}
|
||||
while len(colors) < self.state_count:
|
||||
column = max((i for i in range(self.state_count) if i not in colors),
|
||||
key=lambda i: (len({colors[j] for j in conflicts[i] if j in colors}),
|
||||
len(conflicts[i]), -i))
|
||||
used = {colors[j] for j in conflicts[column] if j in colors}
|
||||
color = 0
|
||||
while color in used:
|
||||
color += 1
|
||||
colors[column] = color
|
||||
ordered = tuple(colors[i] for i in range(self.state_count))
|
||||
for entries in rows:
|
||||
if len({ordered[column] for column in entries}) != len(entries):
|
||||
raise AssertionError('Jacobian coloring contains a row conflict')
|
||||
return JacobianStructure(self.state_count, rows, ordered)
|
||||
@@ -41,6 +41,11 @@ def execute_native(build: NativeBuild, config: SolveIVPConfig, sample_step: floa
|
||||
command.append("--solve-only")
|
||||
creationflags = subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0
|
||||
started = time.perf_counter()
|
||||
# A fast worker may finish before the monitor's first iteration. Honor a
|
||||
# cancellation already requested during preparation before spawning it.
|
||||
cancelled_at = started if cancel_check is not None and cancel_check() else None
|
||||
if cancelled_at is not None:
|
||||
cancel_path.write_text("cancel\n", encoding="ascii")
|
||||
process = subprocess.Popen(command, cwd=build.executable.parent, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,
|
||||
text=True, encoding="utf-8", errors="replace", creationflags=creationflags)
|
||||
@@ -53,7 +58,6 @@ def execute_native(build: NativeBuild, config: SolveIVPConfig, sample_step: floa
|
||||
reader.start()
|
||||
if activity_tracker is not None:
|
||||
activity_tracker.start_integration(config.t_start)
|
||||
cancelled_at = None
|
||||
last_time = config.t_start
|
||||
try:
|
||||
with (run_dir / "worker.log").open("w", encoding="utf-8") as log:
|
||||
|
||||
Reference in new issue
Block a user