Files
SystemSimulationApp/native/components/modules/properties.c
T
ljz 1aac220084 优化 Jacobian 确定性复用并补充性能剖析与平台依赖文档
在单次 Jacobian 构建内按完整输入精确复用储气物性、PH 反算、密度和管路求根结果,保持原有求值副作用、差分政策与失败回退。八路模型求解 CPU 中位数减少 19.27%,循环和不循环的完整原始采样均与恢复基线一致。

增加独立的跨平台时间剖析工具,记录互斥阶段耗时、Newton/LU 统计、矩阵复用与内核复用,保存 UD00 两种工况的调查报告和机器可读汇总。

补充 Windows/Linux 运行、测试、原生编译和剖析所需依赖文档及索引,不修改依赖清单、版本锁或安装环境。

验证:8 项新增专项回归通过;2270 次完整 Jacobian 核对零差异;16 次剖析配对及预热运行保持完整数值一致。既有固定样本哈希失败和 Linux 实机验收限制见报告。
2026-09-16 13:39:53 +08:00

430 lines
22 KiB
C

/* Helium Peng-Robinson and compressible-orifice kernels.
* Helium NASA ranges and continuation are checked against independent
* Amesim 2404 property-library outputs, including high-temperature boundaries.
*/
#include "component_properties_internal.h"
#include "component_constants_internal.h"
#include <math.h>
#include <stddef.h>
#include <string.h>
#define RU 8.31446261815324
#define MOLAR_MASS 0.004002602
#define TC 5.1953
#define PC 227460.0
#define OMEGA (-0.382)
static const double pr_a=.457235583*RU*RU*TC*TC/PC;
static const double pr_b=.07779607*RU*TC/PC;
static const double kappa=.37464+1.54226*OMEGA-.26992*OMEGA*OMEGA;
static const double rg=RU/MOLAR_MASS;
NATIVE_COMPONENT_INTERNAL const NativeMedium helium_medium={1,RU/MOLAR_MASS,2.5*RU/MOLAR_MASS,293.15,0,1.96e-5,293.15,79.4};
NATIVE_COMPONENT_INTERNAL int same_medium(const NativeMedium *a,const NativeMedium *b) {
return a->real_helium==b->real_helium && a->R==b->R && a->cp==b->cp &&
a->Tref==b->Tref && a->slope==b->slope && a->mu==b->mu && a->muT==b->muT && a->S==b->S;
}
void native_properties_init(NativePropertyCache *cache,NativePropertyState *states,size_t capacity) {
cache->states=states;cache->count=0;cache->capacity=states?capacity:0;
cache->temperatures=NULL;
cache->jacobian=NULL;
}
static void observe_temperature(NativePropertyTemperatures *report,const NativeMedium *m,double T,unsigned domains) {
if(!report || !m->real_helium || !isfinite(T))return;
for(int i=0;i<NATIVE_PROPERTY_DOMAINS;i++)if(domains&(1u<<i)) {
if(!(report->seen&(1u<<i)))report->minimum[i]=report->maximum[i]=T;
else {report->minimum[i]=fmin(report->minimum[i],T);report->maximum[i]=fmax(report->maximum[i],T);}
report->seen|=1u<<i;
}
}
static NativePropertyState *property_new(NativePropertyCache *cache,const NativeMedium *m,
double p,double T,NativePropertyState *scratch) {
int valid=p>0 && T>0 && isfinite(p) && isfinite(T);
NativePropertyState *s=valid && cache && cache->count<cache->capacity ? &cache->states[cache->count++] : scratch;
*s=(NativePropertyState){0};s->medium=*m;s->p=p;s->T=T;s->valid=valid?NATIVE_PROPERTY_PT:0;
s->temperatures=cache?cache->temperatures:NULL;
s->jacobian=cache?cache->jacobian:NULL;
return s;
}
NATIVE_COMPONENT_INTERNAL NativePropertyState *property_pt(NativePropertyCache *cache,const NativeMedium *m,
double p,double T,NativePropertyState *scratch) {
if(cache)for(size_t i=0;i<cache->count;i++) {
NativePropertyState *s=&cache->states[i];
if((s->valid&NATIVE_PROPERTY_PT) && s->p==p && s->T==T && same_medium(&s->medium,m))return s;
}
return property_new(cache,m,p,T,scratch);
}
NATIVE_COMPONENT_INTERNAL double property_density(NativePropertyState *s) {
observe_temperature(s->temperatures,&s->medium,s->T,1);
if(!(s->valid&NATIVE_PROPERTY_RHO)) {
if(s->jacobian) {
const NativeMedium *m=&s->medium;
double key[]={s->p,s->T,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
if(!native_jacobian_scalar_get(s->jacobian,NATIVE_JACOBIAN_DENSITY,m->real_helium,key,9,&s->rho)) {
s->rho=native_density(m,s->p,s->T);
native_jacobian_scalar_put(s->jacobian,NATIVE_JACOBIAN_DENSITY,m->real_helium,key,9,s->rho);
}
} else s->rho=native_density(&s->medium,s->p,s->T);
if(s->rho>0 && isfinite(s->rho))s->valid|=NATIVE_PROPERTY_RHO;
}
return s->rho;
}
NATIVE_COMPONENT_INTERNAL double property_viscosity(NativePropertyState *s) {
observe_temperature(s->temperatures,&s->medium,s->T,4);
if(!(s->valid&NATIVE_PROPERTY_MU)) {
s->mu=native_viscosity(&s->medium,s->T,0);
if(s->mu>0 && isfinite(s->mu))s->valid|=NATIVE_PROPERTY_MU;
}
return s->mu;
}
static void remember_gas(NativePropertyCache *cache,const NativeMedium *m,const NativeGas *gas) {
observe_temperature(cache?cache->temperatures:NULL,m,gas->T,3);
/* Below helium's critical temperature retain the existing vapor-root/PH
selection. A future phase-aware medium contract can carry that state. */
if(!cache || (m->real_helium && gas->T<=TC))return;
NativePropertyState scratch,*s=property_pt(cache,m,gas->p,gas->T,&scratch);
if(!(s->valid&NATIVE_PROPERTY_PT) || !isfinite(gas->h))return;
if((s->valid&NATIVE_PROPERTY_H) && s->h!=gas->h)
s=property_new(cache,m,gas->p,gas->T,&scratch);
s->h=gas->h;s->valid|=NATIVE_PROPERTY_H;
if(gas->rho>0 && isfinite(gas->rho)){s->rho=gas->rho;s->valid|=NATIVE_PROPERTY_RHO;}
}
double native_temperature_ph_context(NativePropertyCache *cache,const NativeMedium *m,double p,double h) {
if(cache)for(size_t i=0;i<cache->count;i++) {
NativePropertyState *s=&cache->states[i];
if((s->valid&NATIVE_PROPERTY_H) && s->p==p && s->h==h && same_medium(&s->medium,m)) {
observe_temperature(cache->temperatures,m,s->T,3);return s->T;
}
}
double T;
if(cache && cache->jacobian) {
double key[]={p,h,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
if(!native_jacobian_scalar_get(cache->jacobian,NATIVE_JACOBIAN_PH,m->real_helium,key,9,&T)) {
T=native_temperature_ph(m,p,h);
native_jacobian_scalar_put(cache->jacobian,NATIVE_JACOBIAN_PH,m->real_helium,key,9,T);
}
} else T=native_temperature_ph(m,p,h);
observe_temperature(cache?cache->temperatures:NULL,m,T,3);
if(cache && isfinite(h) && T>0 && isfinite(T) && p>0 && isfinite(p)) {
NativePropertyState scratch,*s=property_pt(cache,m,p,T,&scratch);
if((s->valid&NATIVE_PROPERTY_H) && s->h!=h)s=property_new(cache,m,p,T,&scratch);
s->h=h;s->valid|=NATIVE_PROPERTY_H;
}
return T;
}
double native_density_context(NativePropertyCache *cache,const NativeMedium *m,double p,double T) {
NativePropertyState scratch;return property_density(property_pt(cache,m,p,T,&scratch));
}
static double cube_root(double x) { return x==0 ? 0 : copysign(pow(fabs(x),1.0/3.0),x); }
static void attraction(double T, double *a, double *da, double *dda) {
double tr=T/TC, sr=sqrt(tr), base=1+kappa*(1-sr);
*a=pr_a*base*base;
*da=pr_a*(-base*kappa/(TC*sr));
*dda=pr_a*kappa/(2*TC*TC)*(kappa/tr+base/(tr*sr));
}
static double z_factor(double p,double T) {
double a,da,dda; attraction(T,&a,&da,&dda);
double A=a*p/(RU*RU*T*T), B=pr_b*p/(RU*T);
double ca=-(1-B),cb=A-3*B*B-2*B,cc=-(A*B-B*B-B*B*B);
double pp=cb-ca*ca/3,qq=2*ca*ca*ca/27-ca*cb/3+cc;
double disc=pow(qq/2,2)+pow(pp/3,3),off=-ca/3,roots[3]; int n;
if(disc>1e-14) { roots[0]=cube_root(-qq/2+sqrt(disc))+cube_root(-qq/2-sqrt(disc))+off;n=1; }
else if(fabs(disc)<=1e-14) { double u=cube_root(-qq/2);roots[0]=2*u+off;roots[1]=-u+off;n=2; }
else { if(pp>=0) return NAN; double radius=2*sqrt(-pp/3);
double arg=(3*qq/(2*pp))*sqrt(-3/pp),theta=acos(fmax(-1,fmin(1,arg)))/3;
for(int i=0;i<3;i++) { roots[i]=radius*cos(theta-2*PI*i/3)+off; }
n=3;
}
double z=-INFINITY;for(int i=0;i<n;i++) if(roots[i]>B && isfinite(roots[i])) z=fmax(z,roots[i]);
return isfinite(z)?z:NAN;
}
static double density(double p,double T) { return MOLAR_MASS/(z_factor(p,T)*RU*T/p); }
static double log_volume(double rho) {
double v=MOLAR_MASS/rho,sq=sqrt(2.0);
return log((v+(1+sq)*pr_b)/(v+(1-sq)*pr_b))/(2*sq*pr_b*MOLAR_MASS);
}
static double u_departure(double T,double rho) {
double a,da,dda;attraction(T,&a,&da,&dda);return (T*da-a)*log_volume(rho);
}
static double h_departure(double p,double T) {
double a,da,dda;attraction(T,&a,&da,&dda);
double z=z_factor(p,T),B=pr_b*p/(RU*T),sq=sqrt(2.0);
double dep=RU*T*(z-1)+(T*da-a)*log((z+(1+sq)*B)/(z+(1-sq)*B))/(2*sq*pr_b);
return dep/MOLAR_MASS;
}
/* Amesim 2404 helium_cp_h_s.data, NASA 2002 ideal contribution.
* The two lower ranges have Cp/R=2.5. Amesim shifts the third range's
* constant Cp coefficient and integration constant to preserve Cp and h
* at 6000 K; copying its raw a8 would introduce a ~3.94 J/kg enthalpy jump.
* The PR departure terms below are unchanged.
*/
static double helium_high_cp_r(double T) {
return 3396845/(T*T)-2194.038/T+3.080232+
T*(-8.068958e-5+T*(6.252785e-9+T*(-2.574990e-13+T*4.429960e-18)));
}
static double helium_high_cp_slope_r(double T) {
return -2*3396845/(T*T*T)+2194.038/(T*T)-8.068958e-5+
T*(2*6.252785e-9+T*(-3*2.574990e-13+T*4*4.429960e-18));
}
static double cp_ideal(double T) {
if(T<=6000)return 2.5*rg;
double offset=helium_high_cp_r(6000)-2.5;
if(T<=20000)return rg*(helium_high_cp_r(T)-offset);
/* Amesim's out-of-table continuation: quadratic Cp with matched value
and slope. This is extrapolation, not a wider validity claim. */
double c=helium_high_cp_r(20000)-offset;
double x=(T-20000)*helium_high_cp_slope_r(20000)/c;
return rg*c*(1+x+.5*x*x);
}
static double helium_high_enthalpy_increment(double T) {
const double b=6000,b2=b*b,b3=b2*b,b4=b3*b;
double x=T-b,t2=T*T,t3=t2*T,t4=t3*T;
/* Integrate Cp from the boundary; log1p and factored powers retain
precision when an integrator crosses 6000 K by a very small step. */
return rg*(3396845*x/(T*b)-2194.038*log1p(x/b)+x*(
3.080232-(helium_high_cp_r(b)-2.5)-8.068958e-5*(T+b)/2+
6.252785e-9*(t2+T*b+b2)/3-2.574990e-13*(t3+t2*b+T*b2+b3)/4+
4.429960e-18*(t4+t3*b+t2*b2+T*b3+b4)/5));
}
static double h_ideal(double T) {
if(T<=6000)return rg*(2.5*T-745.375);
double h=rg*(2.5*6000-745.375)+helium_high_enthalpy_increment(fmin(T,20000));
if(T>20000) {
double c=helium_high_cp_r(20000)-helium_high_cp_r(6000)+2.5;
double dt=T-20000,x=dt*helium_high_cp_slope_r(20000)/c;
h+=rg*c*dt*(1+.5*x+x*x/6);
}
return h;
}
static double u_ideal(double T) {
return T<=6000 ? rg*(1.5*T-745.375) : h_ideal(T)-rg*T;
}
static double temperature_ideal(double energy,int enthalpy) {
double c=enthalpy?2.5:1.5,linear=(energy/rg+745.375)/c;
if(linear<=6000 || !isfinite(linear))return linear;
double lo=6000,hi=fmax(linear,6001);
for(int i=0;i<64;i++) {
double value=enthalpy?h_ideal(hi):u_ideal(hi);
if(value>=energy)break;
hi*=2;
}
double T=fmin(linear,hi);
for(int i=0;i<64;i++) {
double value=enthalpy?h_ideal(T):u_ideal(T),residual=value-energy;
if(fabs(residual)<=1e-12*fmax(fabs(energy),1))return T;
if(residual>0)hi=T;else lo=T;
double next=T-residual/(cp_ideal(T)-(enthalpy?0:rg));
if(!isfinite(next) || next<=lo || next>=hi)next=lo+.5*(hi-lo);
T=next;
}
return T;
}
static double temperature_u(double u) { return temperature_ideal(u,0); }
static double temperature_h(double h) { return temperature_ideal(h,1); }
static double pressure_rho(double T,double rho) {
double v=MOLAR_MASS/rho,a,da,dda;attraction(T,&a,&da,&dda);
if(v<=pr_b || T<=0) return NAN;
return RU*T/(v-pr_b)-a/(v*(v+pr_b)+pr_b*(v-pr_b));
}
static NativeGas gas_properties(double m,double U,double V) {
NativeGas g;g.rho=m/V;g.u=U/m;
double T=fmax(temperature_u(g.u),2.2);
for(int i=0;i<16;i++) { double next=fmax(temperature_u(g.u-u_departure(T,g.rho)),2.2);
int done=fabs(next-T)<=1e-10*fmax(T,1);T=next;if(done) break; }
g.T=T;g.p=pressure_rho(T,g.rho);
/* Same energy reference as u_ideal/h_ideal. Avoid a redundant cubic solve
in the single-root region; preserve vapor-root semantics below TC. */
g.h=T>TC ? g.u+g.p/g.rho : h_ideal(T)+h_departure(g.p,T);return g;
}
static double temperature_ph(double p,double h) {
double T=fmax(temperature_h(h),2.2);
for(int i=0;i<16;i++) { double next=fmax(temperature_h(h-h_departure(p,T)),2.2);
int done=fabs(next-T)<=1e-10*fmax(T,1);T=next;if(done) break; }return T;
}
static void local_isentropic(NativePropertyState *s) {
observe_temperature(s->temperatures,&s->medium,s->T,3);
if(s->valid&NATIVE_PROPERTY_ISENTROPIC)return;
double p=s->p,T=s->T,rho=property_density(s),v=MOLAR_MASS/rho,a,da,dda;attraction(T,&a,&da,&dda);
double d=v*(v+pr_b)+pr_b*(v-pr_b);
double dpT=RU/(v-pr_b)-da/d;
double dpR=(-RU*T/pow(v-pr_b,2)+a*2*(v+pr_b)/(d*d))*(-MOLAR_MASS/(rho*rho));
double cv=cp_ideal(T)-rg+T*dda*log_volume(rho);
double cp=cv+T*dpT*dpT/(rho*rho*dpR),gamma=cp/cv;
s->isentropic_factor=p/(rho*dpR*gamma);s->isentropic_exponent=p*(gamma-1)/(gamma*T*dpT);
if(isfinite(s->isentropic_factor) && isfinite(s->isentropic_exponent))s->valid|=NATIVE_PROPERTY_ISENTROPIC;
}
static double isentropic(NativePropertyCache *cache,NativePropertyState *up,double pd) {
local_isentropic(up);if(pd>=up->p)return up->isentropic_factor;
double Td=fmax(up->T*pow(fmax(pd/up->p,1e-12),up->isentropic_exponent),2.2);
NativePropertyState scratch,*down=property_pt(cache,&up->medium,fmax(pd,1),Td,&scratch);
local_isentropic(down);return .5*(up->isentropic_factor+down->isentropic_factor);
}
static double subsonic_cm(double r,double gamma,double rho,double T,double p) {
return sqrt(fmax(2/(1-gamma)*rho*T/p*(pow(r,2*gamma)-pow(r,1+gamma)),0));
}
NATIVE_COMPONENT_INTERNAL void state_valve(NativePropertyCache *cache,NativePropertyState *up,double pd,double *cm,double *vel) {
double p=up->p,T=up->T;pd=fmax(fmin(pd,p),0);
const NativeMedium *m=&up->medium;
double cp=m->cp+m->slope*(T-m->Tref);
double factor=m->real_helium?isentropic(cache,up,pd):(cp-m->R)/cp;
double g=fmax(1e-9,fmin(1-1e-9,factor)),rho=fmax(property_density(up),1e-12);
double r=fmax(pd/p,0),critical=pow(2*g/(g+1),1/(1-g)),eff;
if(r<=critical) { eff=critical;*cm=sqrt(2/(1+g)*rho*T/p)*pow(2*g/(g+1),g/(1-g));*vel=sqrt(2/(1+g)*p/rho); }
else { eff=r;*cm=subsonic_cm(r,g,rho,T,p);*vel=sqrt(fmax(2/(1-g)*p/rho*(1-pow(r,1-g)),0)); }
double ref=subsonic_cm(.9999,g,rho,T,p);
if(*cm>0 && ref>0) { double smooth=tanh(fmax(12*fabs(*cm/ref)*log(eff)/log(.9999),0));*cm*=smooth;*vel*=smooth; }
}
NATIVE_COMPONENT_INTERNAL void medium_valve(NativePropertyCache *cache,const NativeMedium *m,double p,double pd,double T,double *cm,double *vel) {
NativePropertyState scratch,*up=property_pt(cache,m,fmax(p,1),fmax(T,1),&scratch);
state_valve(cache,up,pd,cm,vel);
}
int native_gas_init(double p, double T, double volume, double *mU) {
if (!(p > 0 && T >= 2.2 && volume > 0)) return 0;
double rho = density(p, T);
mU[0] = rho * volume;
mU[1] = mU[0] * (u_ideal(T) + u_departure(T, rho));
return isfinite(mU[0]) && isfinite(mU[1]) && mU[0] > 0;
}
int native_gas(double m, double U, double volume, NativeGas *gas) {
if (!(m > 0 && volume > 0) || !isfinite(U)) return 0;
*gas = gas_properties(m, U, volume);
return gas->p > 0 && isfinite(gas->p) && isfinite(gas->h);
}
int native_gas_context(NativePropertyCache *cache,double m,double U,double V,NativeGas *gas) {
int ok=native_gas(m,U,V,gas);if(ok)remember_gas(cache,&helium_medium,gas);return ok;
}
static double ideal_temperature(const NativeMedium *m, double energy, double c) {
double delta = energy - c*m->Tref;
if (fabs(m->slope) <= 1e-15) return m->Tref + delta/c;
double root = sqrt(fmax(c*c + 2*m->slope*delta, 0));
double a = (-c+root)/m->slope, b = (-c-root)/m->slope;
return m->Tref + (fabs(a)<=fabs(b) ? a : b);
}
int native_medium_init(const NativeMedium *medium, double p, double T, double V,
int legacy_ideal_initial, double *mU) {
if (!(p>0 && T>0 && V>0)) return 0;
if (medium->real_helium && !legacy_ideal_initial) return native_gas_init(p,T,V,mU);
double mass=p*V/(medium->R*T),dt=T-medium->Tref;
double u=medium->real_helium?u_ideal(T):(medium->cp-medium->R)*T+.5*medium->slope*dt*dt;
mU[0]=mass;mU[1]=mass*u;
return isfinite(mU[0]) && isfinite(mU[1]);
}
double native_density(const NativeMedium *m, double p, double T) {
return m->real_helium ? density(p,T) : p/(m->R*T);
}
double native_temperature_ph(const NativeMedium *m, double p, double h) {
return m->real_helium ? temperature_ph(p,h) : ideal_temperature(m,h,m->cp);
}
double native_viscosity(const NativeMedium *m, double T, int diagnostic) {
/* Retain the ABI argument; flow and diagnostics use the same property. */
(void)diagnostic;
if (m->real_helium) {
/* helium_mu.data: NASA 96, microPoise -> Pa.s. Anchor ln(mu)
at each boundary, as Amesim does, to remove coefficient rounding
jumps. The last range is also its out-of-table extrapolation. */
double l1000=.7501594*log(1000)+35.76324/1000-2212.129/1e6+.9212635;
if(T<=1000)return 1e-7*exp(.7501594*log(T)+35.76324/T-2212.129/(T*T)+.9212635);
if(T<=5000)return 1e-7*exp(l1000+.8339417*log(T/1000)+
220.8266*(1/T-1./1000)-52852.59*(1/(T*T)-1e-6));
double l5000=l1000+.8339417*log(5)+220.8266*(1./5000-1./1000)-52852.59*(1./25e6-1e-6);
return 1e-7*exp(l5000+.8631635*log(T/5000)+
962.0518*(1/T-1./5000)-1249870*(1/(T*T)-1./25e6));
}
return m->mu*pow(T/m->muT,1.5)*(m->muT+m->S)/(T+m->S);
}
int native_medium_gas(const NativeMedium *medium, double m, double U, double V, NativeGas *g) {
if (medium->real_helium) return native_gas(m,U,V,g);
if (!(m>0 && V>0)) return 0;
g->u=U/m; g->rho=m/V;
g->T=ideal_temperature(medium,g->u,medium->cp-medium->R);
g->p=g->rho*medium->R*g->T;
double dt=g->T-medium->Tref;
g->h=medium->cp*g->T+.5*medium->slope*dt*dt;
return g->T>0 && isfinite(g->p) && isfinite(g->h);
}
int native_medium_gas_context(NativePropertyCache *cache,const NativeMedium *m,double mass,double U,double V,NativeGas *g) {
int ok=native_medium_gas(m,mass,U,V,g);if(ok)remember_gas(cache,m,g);return ok;
}
int native_polytropic_gas_context(NativePropertyCache *cache,const NativeMedium *m,
double mass,double T,double V,NativeGas *g) {
/* Amesim's polytropic storage uses ideal-gas mass even when the selected
gas supplies real-fluid enthalpy/density to the connected resistances. */
if(!(mass>0 && T>0 && V>0))return 0;
g->T=T;g->p=mass*m->R*T/V;
g->rho=native_density_context(cache,m,g->p,T);
double dt=T-m->Tref;
g->h=m->real_helium?h_ideal(T)+h_departure(g->p,T):m->cp*T+.5*m->slope*dt*dt;
g->u=g->h-g->p/g->rho;
if(!(g->p>0 && g->rho>0 && isfinite(g->p) && isfinite(g->h) && isfinite(g->u)))return 0;
remember_gas(cache,m,g);return 1;
}
int native_jacobian_gas(NativeJacobianGasMemo *memo,NativeCanonicalGas kernel,const NativeMedium *m,
double mass,double thermal,double volume,NativeGas *g,NativeJacobianGasStats *stats) {
double inputs[]={mass,thermal,volume,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
/* Bit keys also distinguish signed zero. No approximate lookup, pointer
identity for medium data, or dependence on component names/state slots. */
if(memo && memo->valid && memo->kernel==kernel && memo->medium_kind==m->real_helium &&
memcmp(memo->inputs,inputs,sizeof(inputs))==0) {
*g=memo->value;
if(stats)stats->reuses++;
return 1;
}
if(stats)stats->evaluations++;
int ok=kernel(NULL,m,mass,thermal,volume,g);
if(ok && memo && !memo->valid) {
memo->kernel=kernel;memo->medium_kind=m->real_helium;
memcpy(memo->inputs,inputs,sizeof(inputs));memo->value=*g;memo->valid=1;
}
/* A perturbed miss never evicts the unperturbed baseline. Failure is
never cached; dense recovery can always evaluate the original kernel. */
return ok;
}
static NativeJacobianScalarEntry *jacobian_scalar_slot(NativeJacobianScalars *cache,int kind,int medium_kind,
const double *inputs,size_t count,uint64_t *key_hash) {
if(!cache || !cache->entries || !cache->capacity || (cache->capacity&(cache->capacity-1)) ||
count>NATIVE_JACOBIAN_SCALAR_KEYS || kind<0 || kind>=NATIVE_JACOBIAN_SCALAR_KINDS)return NULL;
uint64_t hash=UINT64_C(14695981039346656037)^(unsigned)kind;
hash=(hash^(unsigned)medium_kind)*UINT64_C(1099511628211);
for(size_t i=0;i<count;i++) {
uint64_t bits;memcpy(&bits,&inputs[i],sizeof(bits));
hash=(hash^bits)*UINT64_C(1099511628211);hash^=hash>>32;
}
if(!hash)hash=1;
*key_hash=hash;
/* A saturated table or an unlucky cluster must not turn every changed
input into a scan of the whole cache. Insertion uses this same bound,
so every stored entry remains reachable; all other keys fall back. */
size_t probe_limit=cache->capacity<32?cache->capacity:32;
for(size_t i=0;i<probe_limit;i++) {
NativeJacobianScalarEntry *entry=&cache->entries[(hash+i)&(cache->capacity-1)];
if(!entry->hash || (entry->hash==hash && entry->kind==kind && entry->medium_kind==medium_kind &&
entry->input_count==count && !memcmp(entry->inputs,inputs,count*sizeof(double))))return entry;
}
return NULL;
}
int native_jacobian_scalar_get(NativeJacobianScalars *cache,int kind,int medium_kind,
const double *inputs,size_t count,double *value) {
uint64_t hash;
NativeJacobianScalarEntry *entry=jacobian_scalar_slot(cache,kind,medium_kind,inputs,count,&hash);
if(entry && entry->hash) {
*value=entry->value;cache->reuses[kind]++;return 1;
}
if(cache && kind>=0 && kind<NATIVE_JACOBIAN_SCALAR_KINDS)cache->evaluations[kind]++;
return 0;
}
void native_jacobian_scalar_put(NativeJacobianScalars *cache,int kind,int medium_kind,
const double *inputs,size_t count,double value) {
if(!cache || !cache->recording || !isfinite(value))return;
uint64_t hash;
NativeJacobianScalarEntry *entry=jacobian_scalar_slot(cache,kind,medium_kind,inputs,count,&hash);
if(!entry || entry->hash)return;
entry->hash=hash;entry->kind=kind;entry->medium_kind=medium_kind;entry->input_count=count;
memcpy(entry->inputs,inputs,count*sizeof(double));entry->value=value;
}