#include "component_properties_internal.h" #include "component_constants_internal.h" #include static double pipe_rough_limit(double rr) { return rr>0 ? 1/pow(-2*log10(rr/3.7),2) : 0; } static double pipe_friction_prepared(double re, double rr, double rough_limit) { if(re<=0) return 64000000; double lam=64/re; if(re<=89.96829989) return lam; double smooth=1/pow(-1.8*log10(6.9/re),2),turb=smooth; if(rr>0) { double r=re*rr,weight=r*r/(r*r+180*180);turb+=weight*(rough_limit-smooth); } double trans=pow((re-89.96829989)/2741.96700831,8.37293695); return lam+trans/(1+trans)*(turb-lam); } static double pipe_friction(double re, double rr) { return pipe_friction_prepared(re,rr,pipe_rough_limit(rr)); } /* Derivative of the existing friction blend, local to this scalar solve. */ static double pipe_friction_derivative(double re,double rr,double rough,double *df) { double lam=64/re,dl=-lam/re; if(re<=89.96829989){*df=dl;return lam;} double a=-1.8*log10(6.9/re),smooth=1/(a*a); double ds=-2*smooth/a*1.8/(log(10.0)*re),turb=smooth,dt=ds; if(rr>0){double r=re*rr,w=r*r/(r*r+180*180),dw=2*w*(1-w)/re; turb+=w*(rough-smooth);dt=ds*(1-w)+dw*(rough-smooth);} double z=pow((re-89.96829989)/2741.96700831,8.37293695),b=z/(1+z); double db=8.37293695*b*(1-b)/(re-89.96829989); *df=dl*(1-b)+b*dt+db*(turb-lam);return lam+b*(turb-lam); } static double pipe_checked_solution(double re,double f,double K,double rr,double rough, double flow_per_re,NativePipeSolve *status) { double target=sqrt(K/f),change=fabs(target-re); /* Separate the absolute and relative tests so a large flow scale cannot make both sides overflow and accidentally satisfy the stopping test. */ if(!isfinite(target) || !isfinite(target*flow_per_re) || !(change<=re*1e-10 || change*flow_per_re<=1e-13))return NAN; double value=target*target*pipe_friction_prepared(target,rr,rough); status->relative_residual=fabs(value/K-1); if(isfinite(value) && status->relative_residual<=1e-9) { status->converged=1;return target; } return NAN; } double native_pipe_resistance(double K,double rr,double flow_per_re,NativePipeSolve *status) { NativePipeSolve local={0,0,0,INFINITY};if(!status)status=&local; *status=local; if(!(K>=0 && rr>=0 && flow_per_re>0) || !isfinite(K) || !isfinite(rr) || !isfinite(flow_per_re))return NAN; if(K==0){status->converged=1;status->relative_residual=0;return 0;} if(K/64<=89.96829989) { double re=K/64; status->relative_residual=fabs(64*re/K-1); if(re>0 && isfinite(re*flow_per_re) && status->relative_residual<=1e-9) { status->converged=1;return re; } return NAN; } double rough=pipe_rough_limit(rr),lo=0,hi=fmax(sqrt(K/.02),1),df; if(!isfinite(rough) || !isfinite(hi))return NAN; int bracketed=0; for(int i=0;i<128;i++) { double value=hi*hi*pipe_friction_prepared(hi,rr,rough); if(!isfinite(value))return NAN; if(value>=K){bracketed=1;break;} lo=hi;hi*=2; if(!isfinite(hi))return NAN; } /* F(0)=-K; each positive lower endpoint was checked while expanding. Never start iteration with an unchecked or non-finite upper residual. */ if(!bracketed)return NAN; double re=fmax(lo,fmin(sqrt(K/.02),hi)),previous_residual=INFINITY; int previous_newton=0; for(int i=0;i<128;i++) { status->iterations=i+1; double f=pipe_friction_derivative(re,rr,rough,&df),value=re*re*f,F=value-K; if(!(f>0) || !isfinite(f) || !isfinite(value))return NAN; status->relative_residual=fabs(value/K-1); double target=pipe_checked_solution(re,f,K,rr,rough,flow_per_re,status); if(status->converged)return target; if(F>0)hi=re;else lo=re; double slope=2*re*f+re*re*df; double next=slope>0 && isfinite(slope) ? re-F/slope : NAN; /* An in-bracket Newton step is useful only if it reduces |F|. After a step that fails to halve it, bisect the retained bracket; successive Newton steps therefore cannot stagnate near an endpoint. */ int bisect=(previous_newton && fabs(F)>.5*previous_residual) || !(slope>0) || !isfinite(slope) || !isfinite(next) || next<=lo || next>=hi; if(bisect) { next=lo+.5*(hi-lo);status->bisections++; if(next<=lo || next>=hi) { /* Adjacent floating-point endpoints: inspect both actual candidates, then fail if neither meets the original tests. */ double endpoints[]={lo,hi}; for(int j=0;j<2;j++) { double endpoint=endpoints[j]; target=pipe_checked_solution(endpoint,pipe_friction_prepared(endpoint,rr,rough), K,rr,rough,flow_per_re,status); if(status->converged)return target; } return NAN; } } previous_newton=!bisect;previous_residual=fabs(F); re=next; } return NAN; } double native_pipe_flow(const NativeMedium *m, double p1, double p2, double T, double d, double length, double rr, int kind) { return native_pipe_flow_context(NULL,m,p1,p2,T,d,length,rr,kind); } double native_pipe_flow_context(NativePropertyCache *cache,const NativeMedium *m,double p1,double p2,double T, double d,double length,double rr,int kind) { if(fabs(p1-p2)<=1e-8) return 0; double p=fmax(fmax(p1,p2),1),pd=fmin(p1,p2),sign=p1>p2?1:-1; T=fmax(T,1); NativePropertyState scratch,*up=property_pt(cache,m,p,T,&scratch); double area=PI*d*d/4,mu=property_viscosity(up),den=PI*d*mu; /* PNL0001/2/3 share compressible flow and its near-equilibrium smoothing. PNL0003 differs in storage placement, not in the resistance law. */ double cm,vel;state_valve(cache,up,pd,&cm,&vel); if(kind==0) { double lam=pow(area*p*cm,2)/(16*PI*mu*length*T); if(4*lam/den<=1000) return sign*lam; } double base=area*p*cm/sqrt(T),K=pow(4*base/den,2)*d/length; return sign*native_pipe_resistance(K,rr,den/4,NULL)*den/4; } double native_pipe_flow_cached(NativePipeCache *cache, const NativeMedium *m, double p1, double p2, double T, double d, double length, double rr, int kind) { return native_pipe_flow_cached_context(NULL,cache,m,p1,p2,T,d,length,rr,kind); } double native_pipe_flow_cached_context(NativePropertyCache *properties,NativePipeCache *cache,const NativeMedium *m, double p1,double p2,double T,double d,double length,double rr,int kind) { if(cache->valid && cache->p1==p1 && cache->p2==p2 && cache->T==T && cache->diameter==d && cache->length==length && cache->roughness==rr && cache->kind==kind && same_medium(&cache->medium,m)) return cache->flow; double result=properties?native_pipe_flow_context(properties,m,p1,p2,T,d,length,rr,kind): native_pipe_flow(m,p1,p2,T,d,length,rr,kind); cache->valid=0; if(isfinite(result)) { cache->medium=*m;cache->p1=p1;cache->p2=p2;cache->T=T; cache->diameter=d;cache->length=length;cache->roughness=rr;cache->kind=kind; cache->flow=result;cache->valid=1; } return result; } void native_pipe_diagnostics(const NativeMedium *m, double q, double p, double T, double d, double length, double rr, int diagnostic, double *r) { native_pipe_diagnostics_context(NULL,m,q,p,T,d,length,rr,diagnostic,r); } void native_pipe_diagnostics_context(NativePropertyCache *cache,const NativeMedium *m,double q,double p,double T, double d,double length,double rr,int diagnostic,double *r) { (void)diagnostic; NativePropertyState scratch,*state=property_pt(cache,m,p,T,&scratch); double area=PI*d*d/4,re=4*fabs(q)/(PI*d*property_viscosity(state)),ff=pipe_friction(re,rr); r[0]=re; r[1]=fabs(q)*sqrt(T)/fmax(sqrt(d/(length*ff))*area*p,1e-18); r[2]=q/(fmax(property_density(state),1e-12)*area);r[3]=ff; }