Replace Python numerical kernels with native C execution

This commit is contained in:
ljz committed 2026-09-10 01:12:18 +08:00
1 parent 48da6be21c
commit 3b38f73fe0
227 files changed
+16801 -75499

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
# C 数值后端
后端 Python 校验 XML、检查连接、生成系统专用 C 并编译;独立 EXE 执行完整数值循环。原生运行不调用 Python。当前发布构建支持 Windows x64。
## 启用网页后端
当前默认使用 `native`。也可在启动后端的 PowerShell 中显式设置:
```powershell
$env:SIMULATION_NUMERIC_ENGINE = 'native'
$env:SIMULATION_NATIVE_CC = 'F:/Projects/mingw64/bin/gcc.exe'
$env:SUNDIALS_ROOT = 'F:/Anaconda/Library'
.venv-win/Scripts/python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000
```
沿用 `/api/system-xml/simulate` 与 `/api/system-xml/simulate-stream`。`native` 遇到不支持的组件或方法时报告错误,不静默切换。旧 Python 后端已退役,显式选择 `python` 会报错。
`bat/start-backend.bat`、`bat/start-backend.sh` 及其 `start-all` 上层入口默认设置 `native`,保留明确指定的环境变量覆盖。直接通过 uvicorn 启动也使用相同的默认选择。启动日志显示所选内核;C 模式只检查工具链与 XML Schema,不再预热 SciPy 积分/代数求解器。工具链检查失败会记录启动诊断,编辑器仍可使用,提交仿真时会明确报错,不会自动调用 Python 数值后端。
通用诊断包含 `stateCount`、`sampleCount`。`pressureFlow`、`stream` 属于 Python 后端的可选诊断;C 后端不报告未计算的方程残差,网页仅在该值存在时显示它。
原生运行库已在本机 GCC 8.1 / SUNDIALS 7.4.0 验证。构建需要对应 C 头文件、导入库和 DLL,单纯安装 Python 包不能代替这些文件。构建器根据环境变量或当前 Python 基础环境查找 SUNDIALS,根据环境变量或 PATH 查找 GCC。
## 独立生成与运行
```powershell
.venv-win/Scripts/python.exe -m app.simulation.native_codegen tests/data/native-skill-test.xml --output-dir test/native-v1/example-run --runs 3 --solve-only
```
输入支持 XML,或带普通算术表达式的工程 JSON。JSON 普通数字已经是 SI 值;算术表达式按编辑器所选单位换算,例如 `3.14*10**2/4 mm2` 转为 `0.0000785 m2`。较复杂的表达式应先在网页导出 XML。CLI 不执行任意 Python/JavaScript 表达式。
输出目录包含 `input.xml`、`model-manifest.json`、`program/`、预热和各次运行结果,以及 `summary.json`。`program/` 包含 EXE、生成的模型 C/头文件、SUNDIALS DLL 和依赖声明,可以复制整目录独立运行:
```powershell
test/native-v1/example-run/program/model.exe --method RK45 --start 0 --stop 10 --sample-step 0.02 --max-step 0.001 --rtol 1e-6 --output test/native-v1/standalone-result.json
```
EXE 不需要 Python、SciPy、XML 或原工程文件。DLL 需要与 EXE 一同保留。默认运行设置是 RK45、0–10 s、最大步长 0.001 s;按需要传入运行选项。
`--solve-only` 关闭轨迹采样,只输出最终状态与诊断。`solveSeconds` 是程序内部数值求解墙钟时间,包含求解必需的 RHS 和事件定位,排除模型初始化、结果投影和文件写入;`processWallSeconds` 另含进程启动与结果处理。预热一次后报告三次求解的中位数。
## 能力与限制
- 已实现当前注册的 27 类组件:22 类 Amesim 公开组件(含空气、氦气两种介质定义)和 5 类实验组件。完整清单及验证说明见 [组件覆盖记录](../docs/other/C内核组件库覆盖记录.md)。介质定义在编译期选择对应的 C 物性函数。
- 管路覆盖 PNL00R、PNL0001/2/3;阀覆盖 PNOR001、固定/信号开度 PNVO001 及面积/Cv/Kv 模式;连接件覆盖 PN3NODE2、P4NODE2、LMECHN1。支持串联阻力的压力求解、节点焓混合及温度参考、刚性质量合并、兼容管路容腔的等密度状态投影。
- MECMAS21 支持现有 Python 方程中的摩擦、风阻、柔性限位和 `stoptype=1/2/3/4`,含反弹系数与速度阈值;气腔和管路支持换热。LSTP00A 接受两种刚度模式及接触力符号模式,严格沿用当前组件方程。已有参数中尚未参与 Python 方程的物理效应不会在 C 端凭空补造,详见覆盖记录。
- 扩展编译器上限 1024 状态、16384 输出;无连续状态的信号系统使用隐藏常量状态驱动输出。气动网络必须有压力状态锚点;独立气腔之间不能无阻力直接相连。兼容固定管路容腔是已实现的合并例外。闭合未收敛或方程欠定时明确失败,不静默回退。
- 支持原生 RK45 与 CVODE BDF。CVODE 继续使用默认数值雅可比和稠密线性求解;本次删除不改变 C 求解器的雅可比策略。
- 默认 `rtol=1e-6`、气体状态 `atol=1e-8`、机械状态 `atol=1e-12`。CLI 可覆盖 rtol;本版不支持自定义 atol 或 first_step。不同积分器相同局部容差不保证全局曲线误差完全相同。
- 时间信号显式分段,塑性/反弹端挡用稠密插值定位并重启。试探 RHS 不修改已接受状态。柔性接触沿用现有分段力公式,不改变刚度或阻尼来提速。
- 每任务独立进程,支持进度、取消及超时。进程崩溃不会作为成功返回,受控失败保留最后接受状态。
- 编译缓存位于 `app/data/native-builds/`,校验模型、组件合同、源码、编译选项及依赖哈希。本版参数特化入 C,修改模型数值参数会重新编译;修改时间、步长、rtol、采样选项可复用 EXE。
## 代码职责
- `app/simulation/native_codegen/input.py`:CLI 输入适配。
- `contracts.py`:逐组件 C 实现版本白名单,新增模型或版本不会自动视为已支持。
- `compiler.py` / `extended.py`:能力检查、状态/输出布局、连接分组、常系数约束消元、C 生成。保留已验证的简单拓扑快速生成路径,两条路径均只运行 C 数值代码。
- `build.py` / `runner.py`:编译缓存、依赖打包、隔离执行与结果适配。
- `native/components/kernels.c`:空气/氦气物性、阀与管路流量、信号、摩擦接触和端挡原语。
- `native/runtime/`:RK45、CVODE 适配、事件定位、采样与 CLI。
- `app/simulation/backends.py` / `results.py`:共用执行入口与结果合同。
执行 `.venv-win/Scripts/python.exe -m unittest tests.test_native_catalog tests.test_native_codegen tests.test_simulation_warmup` 可验证全部注册合同、空气/氦气正反向流、换热/摩擦、节点和串联闭合、刚性与管路状态合并、反弹事件、纯信号系统、缓存、独立 EXE、取消及默认 C 的 XML 接口。新增组件库对照测试需要可用的 C 工具链。
## Python 保留范围
- `app/main.py`、`system_xml.py`:HTTP、XML 校验与结果返回。
- `registry.py`、`core/`、`components/`、`systems/network.py`:参数、端口、介质常量、输出与方程结构声明;不执行模型数值公式。
- `native_codegen/`:生成系统 C、构建缓存、管理独立进程。
- `config.py`、`sampling.py`、`results.py`:配置、进度、采样合法性和结果合同。
- `performance.py`:Python 编排阶段计时,求解耗时读取 C 报告。
`solvers/`、`systems/generic.py`、旧示例、Python 物性/流量/机械公式和物性缓存已删除。数值回归读取 `tests/data/native-python-reference.json` 的 50 个网络、112 组冻结参考状态,不需要 Python 求解器。旧算法及历史对照资料可从 Git 历史找回。
测试安装:`python -m pip install -r requirements-test.txt`。当前完整数值测试要求 Windows x64、GCC 与 SUNDIALS;Linux 尚不提供 EXE 构建。
+89
View File
@@ -0,0 +1,89 @@
RK45 tableau, dense-output coefficients and step-control logic are adapted
from the locally installed SciPy integrate/_ivp/rk.py and common.py.
The implementation is rewritten in C; SciPy is not linked or required.
Copyright (c) 2001-2002 Enthought, Inc. 2003, SciPy Developers.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SUNDIALS 7.4.0
BSD 3-Clause License
Copyright (c) 2002-2025, Lawrence Livermore National Security and Southern Methodist University.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This work was produced under the auspices of the U.S. Department of
Energy by Lawrence Livermore National Laboratory under Contract
DE-AC52-07NA27344.
This work was prepared as an account of work sponsored by an agency of
the United States Government. Neither the United States Government nor
Lawrence Livermore National Security, LLC, nor any of their employees
makes any warranty, expressed or implied, or assumes any legal liability
or responsibility for the accuracy, completeness, or usefulness of any
information, apparatus, product, or process disclosed, or represents that
its use would not infringe privately owned rights.
Reference herein to any specific commercial product, process, or service
by trade name, trademark, manufacturer, or otherwise does not necessarily
constitute or imply its endorsement, recommendation, or favoring by the
United States Government or Lawrence Livermore National Security, LLC.
The views and opinions of authors expressed herein do not necessarily
state or reflect those of the United States Government or Lawrence
Livermore National Security, LLC, and shall not be used for advertising
or product endorsement purposes.
+289
View File
@@ -0,0 +1,289 @@
/* Helium Peng-Robinson and compressible-orifice kernels.
* Ported from the project's Python physical equations; validated independently
* against Python at ordinary, reverse-flow and contact trial states.
*/
#include "kernels.h"
#include <math.h>
#include <stddef.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 PI=3.1415926535897932384626433832795;
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;
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;
}
static double u_ideal(double T) { return rg*(1.5*T-745.375); }
static double h_ideal(double T) { return rg*(2.5*T-745.375); }
static double temperature_u(double u) { return (u/rg+745.375)/1.5; }
static double temperature_h(double h) { return (h/rg+745.375)/2.5; }
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);g.h=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(double p,double T,double *factor,double *exponent) {
double rho=density(p,T),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=1.5*rg+T*dda*log_volume(rho);
double cp=cv+T*dpT*dpT/(rho*rho*dpR),gamma=cp/cv;
*factor=p/(rho*dpR*gamma);*exponent=p*(gamma-1)/(gamma*T*dpT);
}
static double isentropic(double p,double T,double pd) {
double f,e,fd,ed;local_isentropic(p,T,&f,&e);if(pd>=p) return f;
double Td=fmax(T*pow(fmax(pd/p,1e-12),e),2.2);
local_isentropic(fmax(pd,1),Td,&fd,&ed);return .5*(f+fd);
}
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));
}
static void valve(double p,double pd,double T,double *cm,double *vel) {
p=fmax(p,1);pd=fmax(fmin(pd,p),0);T=fmax(T,1);
double g=fmax(1e-9,fmin(1-1e-9,isentropic(p,T,pd))),rho=fmax(density(p,T),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; }
}
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_orifice(double p1, double p2, double h1, double h2,
double cq_area, double opening, double *flow,
double *cm, double *velocity) {
int forward = p1 >= p2;
double p = forward ? p1 : p2, pd = forward ? p2 : p1;
double T = temperature_ph(fmax(p, 1), forward ? h1 : h2);
valve(p, pd, T, cm, velocity);
double sign = forward ? 1 : -1;
*velocity *= sign;
*flow = opening == 0 || fabs(p1-p2) <= 1e-8 ? 0 :
sign * cq_area * opening * fmax(p, 1) * *cm / sqrt(fmax(T, 1));
if (opening == 0) *velocity = 0;
return isfinite(*flow) && isfinite(*cm) && isfinite(*velocity);
}
double native_contact(double penetration, double velocity, double stiffness,
double damping, double pdis, int signed_force) {
if (penetration <= 0) return 0;
double fraction = pdis > 0 ? -expm1(-penetration / pdis) : 1;
double force = stiffness * penetration + fraction * damping * velocity;
return signed_force == 1 ? force : fmax(force, 0);
}
void native_stop_motion(double x, double v, double lower, double upper,
double *acceleration, double *velocity) {
double vt = 1e-12 * fmax(fabs(v), 1);
if ((x <= lower + 1e-12*fmax(fabs(lower),1) && v <= vt && *acceleration <= 0) ||
(x >= upper - 1e-12*fmax(fabs(upper),1) && v >= -vt && *acceleration >= 0)) {
*acceleration = 0;
*velocity = 0;
}
}
double native_signal(double t, double start, int stages, int cyclic, const double *data) {
double elapsed = fmax(t-start,0), duration = 0, offset = 0;
for (int i=0;i<stages;i++) duration += data[16+i];
if (cyclic && duration > 0) elapsed = fmod(elapsed,duration);
for (int i=0;i<stages;i++) {
double d = data[16+i];
if (elapsed < offset+d || i == stages-1)
return d <= 0 ? data[8+i] : data[i] + (elapsed-offset)/d*(data[8+i]-data[i]);
offset += d;
}
return data[8+stages-1];
}
double native_signal_break(double t, double end, double start, int stages,
int cyclic, const double *data) {
double duration=0, offset=0, result=end;
for (int i=0;i<stages;i++) duration += data[16+i];
for (int i=0;i<stages;i++) {
double event=start+offset;
if (cyclic && duration > 0 && event <= t) {
double cycle=fmax(0,floor((t-event)/duration)+1);
event += cycle*duration;
if (event <= t) event += duration;
}
if (event > t) result=fmin(result,event);
offset += data[16+i];
}
return result;
}
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) {
if (diagnostic && m->real_helium)
return 1e-7*exp(.7501594*log(T)+35.76324/T-2212.129/(T*T)+.9212635);
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);
}
static void medium_valve(const NativeMedium *m, double p, double pd, double T, double *cm, double *vel) {
if (m->real_helium) { valve(p,pd,T,cm,vel); return; }
p=fmax(p,1); pd=fmax(fmin(pd,p),0); T=fmax(T,1);
double cp=m->cp+m->slope*(T-m->Tref);
double g=fmax(1e-9,fmin(1-1e-9,(cp-m->R)/cp)),rho=fmax(native_density(m,p,T),1e-12);
double r=pd/p,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; }
}
int native_medium_orifice(const NativeMedium *m, double p1, double p2, double h1, double h2,
double area, double opening, double *q, double *cm, double *v) {
double p=fmax(p1,p2),pd=fmin(p1,p2),sign=p1>=p2?1:-1;
double T=fmax(native_temperature_ph(m,fmax(p,1),p1>=p2?h1:h2),1);
medium_valve(m,p,pd,T,cm,v);
*q=fabs(p1-p2)<=1e-8?0:sign*area*opening*fmax(p,1)*(*cm)/sqrt(T);
*v*=fabs(opening)<=1e-12?0:sign;
return isfinite(*q) && isfinite(*cm) && isfinite(*v);
}
static double pipe_friction(double re, double rr) {
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*(1/pow(-2*log10(rr/3.7),2)-smooth); }
double trans=pow((re-89.96829989)/2741.96700831,8.37293695);
return lam+trans/(1+trans)*(turb-lam);
}
double native_pipe_flow(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);
double area=PI*d*d/4,mu=native_viscosity(m,T,0),den=PI*d*mu;
if(kind==3) { /* PNL0003: Darcy loss between two stored states. */
double rho=fmax(native_density(m,p,T),1e-12),dp=fabs(p1-p2),lo=0,hi=1e-9;
while(pipe_friction(4*hi/den,rr)*(length/d)*hi*hi/(2*rho*area*area)<dp) {
hi*=10; if(hi>=1000) return sign*1000;
}
for(int i=0;i<48;i++) { double q=.5*(lo+hi);
if(pipe_friction(4*q/den,rr)*(length/d)*q*q/(2*rho*area*area)<dp) lo=q;else hi=q;
}
return sign*.5*(lo+hi);
}
double cm,vel;medium_valve(m,p,pd,T,&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),q=sqrt(d/(length*.02))*base;
for(int i=0;i<(kind==0?64:16);i++) {
double next=sqrt(d/(length*pipe_friction(4*fabs(q)/den,rr)))*base;
if(fabs(next-q)<=fmax(1e-12,fabs(q)*1e-9)) return sign*next;
q=.5*(q+next);
}
return sign*q;
}
void native_pipe_diagnostics(const NativeMedium *m, double q, double p, double T,
double d, double length, double rr, int diagnostic, double *r) {
double area=PI*d*d/4,re=4*fabs(q)/(PI*d*native_viscosity(m,T,0)),ff=pipe_friction(re,rr);
r[0]=4*fabs(q)/(PI*d*native_viscosity(m,T,diagnostic));
r[1]=fabs(q)*sqrt(T)/fmax(sqrt(d/(length*ff))*area*p,1e-18);
r[2]=q/(fmax(native_density(m,p,T),1e-12)*area);r[3]=ff;
}
double native_limit_force(double penetration, double velocity, double stiffness,
double damping, double depth, int signed_force) {
if(penetration<=0) return 0;
double force=stiffness*penetration+(depth>0?fmin(penetration/depth,1):1)*damping*velocity;
return signed_force==1?force:fmax(force,0);
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef NATIVE_KERNELS_H
#define NATIVE_KERNELS_H
typedef struct { double p, T, rho, u, h; } NativeGas;
typedef struct {
int velocity_index;
double lower, upper;
double lower_restitution, upper_restitution, lower_threshold, upper_threshold;
} NativeStop;
/* Constants are emitted per medium instance by the model compiler. */
typedef struct { int real_helium; double R, cp, Tref, slope, mu, muT, S; } NativeMedium;
int native_medium_init(const NativeMedium *, double p, double T, double V, int legacy_ideal_initial, double *mU);
double native_density(const NativeMedium *, double p, double T);
double native_temperature_ph(const NativeMedium *, double p, double h);
double native_viscosity(const NativeMedium *, double T, int diagnostic);
int native_medium_gas(const NativeMedium *, double m, double U, double V, NativeGas *);
int native_medium_orifice(const NativeMedium *, double p1, double p2, double h1, double h2,
double cq_area, double opening, double *q, double *cm, double *v);
double native_pipe_flow(const NativeMedium *, double p1, double p2, double T,
double diameter, double length, double roughness, int kind);
void native_pipe_diagnostics(const NativeMedium *, double q, double p, double T,
double diameter, double length, double roughness,
int diagnostic, double *result);
double native_limit_force(double penetration, double velocity, double stiffness,
double damping, double depth, int signed_force);
int native_gas_init(double p, double T, double volume, double *mU);
int native_gas(double m, double U, double volume, NativeGas *gas);
int native_orifice(double p1, double p2, double h1, double h2,
double cq_area, double opening, double *flow,
double *cm, double *velocity);
double native_contact(double penetration, double velocity, double stiffness,
double damping, double pdis, int signed_force);
void native_stop_motion(double x, double v, double lower, double upper,
double *acceleration, double *velocity);
double native_signal(double t, double start, int stages, int cyclic, const double *data);
double native_signal_break(double t, double end, double start, int stages,
int cyclic, const double *data);
#endif
+34
View File
@@ -0,0 +1,34 @@
#ifndef NATIVE_RUNTIME_H
#define NATIVE_RUNTIME_H
#include "model.h"
#include <stddef.h>
typedef struct {
double start, stop, sample_step, max_step, rtol, timeout;
int bdf, record_samples;
const char *cancel_path;
} NativeOptions;
typedef struct {
NativeOptions options;
double *times, *states;
size_t count, capacity, sample_index;
double final_time, final_state[NSTATES];
double wall_start, cpu_start, solve_seconds, solve_cpu_seconds, last_progress;
double max_accepted_step;
unsigned long nfev, accepted, rejected, events, starts, njev, nlu;
int status; /* 0 completed, 1 cancelled, 2 failed */
const char *message;
} NativeRun;
typedef int (*NativeDense)(void *context, double t, double *state);
double native_wall_time(void);
double native_cpu_time(void);
int native_poll(NativeRun *run, double time);
int native_rhs(NativeRun *run, double t, const double *y, double *dy);
int native_append(NativeRun *run, double t, const double *y);
int native_accept(NativeRun *run, double t, double next, const double *old,
const double *trial, NativeDense dense, void *context,
double *accepted_time, double *accepted_state);
int native_rk45(NativeRun *run);
int native_bdf(NativeRun *run);
int native_solve(NativeRun *run);
void native_run_free(NativeRun *run);
#endif
+158
View File
@@ -0,0 +1,158 @@
#include "runtime.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#endif
double native_wall_time(void) {
#ifdef _WIN32
LARGE_INTEGER value, frequency;
QueryPerformanceCounter(&value); QueryPerformanceFrequency(&frequency);
return (double)value.QuadPart / (double)frequency.QuadPart;
#else
struct timespec value; clock_gettime(CLOCK_MONOTONIC, &value);
return value.tv_sec + value.tv_nsec*1e-9;
#endif
}
double native_cpu_time(void) {
#ifdef _WIN32
FILETIME creation, exit_time, kernel, user;
GetProcessTimes(GetCurrentProcess(), &creation, &exit_time, &kernel, &user);
ULARGE_INTEGER k, u; k.LowPart=kernel.dwLowDateTime; k.HighPart=kernel.dwHighDateTime;
u.LowPart=user.dwLowDateTime; u.HighPart=user.dwHighDateTime;
return (double)(k.QuadPart+u.QuadPart)*1e-7;
#else
return (double)clock()/CLOCKS_PER_SEC;
#endif
}
int native_poll(NativeRun *r, double t) {
double now=native_wall_time();
if (r->status) return 0;
if (r->options.timeout > 0 && now-r->wall_start > r->options.timeout) {
r->status=2; r->message="Native solve exceeded its time limit."; return 0;
}
if (now-r->last_progress >= 0.1) {
r->last_progress=now;
if (r->options.cancel_path) {
FILE *f=fopen(r->options.cancel_path,"rb");
if (f) { fclose(f); r->status=1; r->message="Simulation cancelled."; return 0; }
}
fprintf(stderr,"{\"phase\":\"integrating\",\"time\":%.17g,\"nfev\":%lu,\"acceptedSteps\":%lu}\n",t,r->nfev,r->accepted);
fflush(stderr);
}
return 1;
}
int native_rhs(NativeRun *r, double t, const double *y, double *dy) {
double w[NOUTPUTS];
r->nfev++;
return model_eval(t,y,dy,w);
}
int native_append(NativeRun *r, double t, const double *y) {
r->final_time=t; memcpy(r->final_state,y,NSTATES*sizeof(double));
if (!r->options.record_samples) return 1;
if (r->count && r->times[r->count-1] == t) r->count--;
if (r->count == r->capacity) {
size_t cap=r->capacity ? r->capacity*2 : 1024;
if (cap > 2000000 || cap > 268435456u / NSTATES / sizeof(double)) return 0;
double *times=realloc(r->times,cap*sizeof(double));
if (!times) return 0;
r->times=times;
double *states=realloc(r->states,cap*NSTATES*sizeof(double));
if (!states) return 0;
r->states=states; r->capacity=cap;
}
r->times[r->count]=t;
memcpy(r->states+r->count*NSTATES,y,NSTATES*sizeof(double));
r->count++; return 1;
}
static double locate(int idx, double bound, int lower, double left, double right,
NativeDense dense, void *context) {
double state[NSTATES];
for (int i=0;i<60;i++) {
double mid=.5*(left+right);
if (!dense(context,mid,state)) return NAN;
if (lower ? state[idx] <= bound : state[idx] >= bound) right=mid;
else left=mid;
}
return right;
}
int native_accept(NativeRun *r, double t, double next, const double *old,
const double *trial, NativeDense dense, void *context,
double *accepted_time, double *accepted_state) {
double when[2*(NSTOPS+1)], bounds[2*(NSTOPS+1)];
double restitution[2*(NSTOPS+1)], thresholds[2*(NSTOPS+1)];
int indices[2*(NSTOPS+1)], count=0;
for (int j=0;j<NSTOPS;j++) {
NativeStop s=model_stops[j]; int v=s.velocity_index, x=v+1;
double vt0=1e-12*fmax(fabs(old[v]),1), vt1=1e-12*fmax(fabs(trial[v]),1);
for (int side=0;side<2;side++) {
int lower=side==0; double bound=lower?s.lower:s.upper;
double tol=1e-12*fmax(fabs(bound),1), at=INFINITY;
if (lower ? (old[x]<=bound+tol && old[v]<-vt0) : (old[x]>=bound-tol && old[v]>vt0)) at=t;
else if (lower ? (old[x]>bound+tol && trial[x]<=bound) : (old[x]<bound-tol && trial[x]>=bound))
at=locate(x,bound,lower,t,next,dense,context);
else if (lower ? (old[x]<=bound && old[v]>vt0 && trial[v]<-vt1 && trial[x]<=bound) :
(old[x]>=bound && old[v]<-vt0 && trial[v]>vt1 && trial[x]>=bound)) {
double turn=locate(v,0,lower,t,next,dense,context);
at=locate(x,bound,lower,turn,next,dense,context);
}
if (isfinite(at)) {
when[count]=at; bounds[count]=bound; indices[count]=v;
restitution[count]=lower?s.lower_restitution:s.upper_restitution;
thresholds[count]=lower?s.lower_threshold:s.upper_threshold;
count++;
}
}
}
double stop=next;
for (int i=0;i<count;i++) stop=fmin(stop,when[i]);
if (r->options.record_samples) {
double st=r->options.start+r->sample_index*r->options.sample_step;
while (st<=r->options.stop && (count ? st<stop : st<=stop)) {
double sy[NSTATES];
if (!dense(context,st,sy) || !native_append(r,st,sy)) return -1;
r->sample_index++; st=r->options.start+r->sample_index*r->options.sample_step;
}
}
if (count) {
if (!dense(context,stop,accepted_state)) return -1;
for (int i=0;i<count;i++) if (fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
double incoming=accepted_state[indices[i]];
accepted_state[indices[i]]=fabs(incoming)<=thresholds[i]?0:-restitution[i]*incoming;
accepted_state[indices[i]+1]=bounds[i];
}
if (!native_append(r,stop,accepted_state)) return -1;
while (r->options.start+r->sample_index*r->options.sample_step<=stop) r->sample_index++;
r->events++;
} else memcpy(accepted_state,trial,NSTATES*sizeof(double));
*accepted_time=stop;
r->final_time=stop; memcpy(r->final_state,accepted_state,NSTATES*sizeof(double));
return count ? 1 : 0;
}
int native_solve(NativeRun *r) {
double y[NSTATES];
if (!model_init(y)) { r->status=2; r->message="Native model initialization failed."; return 0; }
r->sample_index=1;
if (!native_append(r,r->options.start,y)) { r->status=2; r->message="Cannot allocate samples."; return 0; }
r->wall_start=native_wall_time(); r->cpu_start=native_cpu_time();
r->last_progress=r->wall_start-1;
int ok=r->options.bdf ? native_bdf(r) : native_rk45(r);
r->solve_seconds=native_wall_time()-r->wall_start;
r->solve_cpu_seconds=native_cpu_time()-r->cpu_start;
if (!ok && !r->status) { r->status=2; r->message="Native integration failed to advance."; }
if (!native_append(r,r->final_time,r->final_state)) { r->status=2; r->message="Cannot save final state."; }
if (!r->message) r->message=r->status ? "Native integration failed." : "Simulation completed.";
return !r->status;
}
void native_run_free(NativeRun *r) { free(r->times); free(r->states); }
+97
View File
@@ -0,0 +1,97 @@
/* CVODE owns its default numerical Jacobian and dense linear solver.
* No project Jacobian, sparsity or derivative policy is changed here.
*/
#include "runtime.h"
#include <cvode/cvode.h>
#include <cvode/cvode_ls.h>
#include <nvector/nvector_serial.h>
#include <sunmatrix/sunmatrix_dense.h>
#include <sunlinsol/sunlinsol_dense.h>
#include <math.h>
#include <string.h>
typedef struct { void *solver; N_Vector scratch; } CvDense;
static int cv_dense(void *context, double t, double *out) {
CvDense *d=context;
if (CVodeGetDky(d->solver,t,0,d->scratch) < 0) return 0;
memcpy(out,N_VGetArrayPointer(d->scratch),NSTATES*sizeof(double));
return 1;
}
static int cv_rhs(sunrealtype t, N_Vector y, N_Vector dy, void *context) {
NativeRun *r=context;
if (!native_poll(r,r->final_time)) return -1;
return native_rhs(r,t,N_VGetArrayPointer(y),N_VGetArrayPointer(dy)) ? 0 : 1;
}
static void counters(NativeRun *r, void *solver) {
long int value=0;
CVodeGetNumErrTestFails(solver,&value); r->rejected+=(unsigned long)value;
CVodeGetNumJacEvals(solver,&value); r->njev+=(unsigned long)value;
CVodeGetNumLinSolvSetups(solver,&value); r->nlu+=(unsigned long)value;
}
int native_bdf(NativeRun *r) {
SUNContext ctx=NULL;
if (SUNContext_Create(SUN_COMM_NULL,&ctx)) return 0;
N_Vector y=N_VNew_Serial(NSTATES,ctx), atol=N_VNew_Serial(NSTATES,ctx), scratch=N_VNew_Serial(NSTATES,ctx);
SUNMatrix matrix=NULL; SUNLinearSolver linear=NULL; void *solver=NULL;
int success=0;
if (!y || !atol || !scratch) goto cleanup;
memcpy(N_VGetArrayPointer(y),r->final_state,NSTATES*sizeof(double));
memcpy(N_VGetArrayPointer(atol),model_atol,NSTATES*sizeof(double));
matrix=SUNDenseMatrix(NSTATES,NSTATES,ctx);
if (!matrix) goto cleanup;
linear=SUNLinSol_Dense(y,matrix,ctx);
if (!linear) goto cleanup;
solver=CVodeCreate(CV_BDF,ctx);
if (!solver) goto cleanup;
double t=r->options.start;
if (CVodeInit(solver,cv_rhs,t,y)<0 || CVodeSetUserData(solver,r)<0 ||
CVodeSVtolerances(solver,r->options.rtol,atol)<0 ||
CVodeSetLinearSolver(solver,linear,matrix)<0 ||
CVodeSetMaxStep(solver,r->options.max_step)<0) goto cleanup;
r->starts++;
CvDense dense={solver,scratch};
while (t<r->options.stop) {
double boundary=model_next_break(t,r->options.stop);
double end=boundary<r->options.stop?nextafter(boundary,-INFINITY):boundary;
if (CVodeSetStopTime(solver,end)<0) goto cleanup;
while (t<end) {
if (!native_poll(r,t) || r->accepted>10000000 || r->events>10000) goto cleanup;
double old[NSTATES], accepted[NSTATES], next=t;
memcpy(old,N_VGetArrayPointer(y),sizeof(old));
int flag=CVode(solver,end,y,&next,CV_ONE_STEP);
if (flag<0 || next<=t) goto cleanup;
r->accepted++; r->max_accepted_step=fmax(r->max_accepted_step,next-t);
int impact=native_accept(r,t,next,old,N_VGetArrayPointer(y),cv_dense,&dense,&t,accepted);
if (impact<0) goto cleanup;
memcpy(N_VGetArrayPointer(y),accepted,sizeof(accepted));
if (impact) {
counters(r,solver);
if (CVodeReInit(solver,t,y)<0) goto cleanup;
r->starts++;
}
}
t=boundary; r->final_time=t;
memcpy(r->final_state,N_VGetArrayPointer(y),NSTATES*sizeof(double));
double sample=r->options.start+r->sample_index*r->options.sample_step;
if (r->options.record_samples && sample<=t && sample<=r->options.stop) {
if (!native_append(r,sample,r->final_state)) goto cleanup;
r->sample_index++;
}
if (t<r->options.stop) {
counters(r,solver);
if (CVodeReInit(solver,t,y)<0) goto cleanup;
r->starts++;
}
}
success=1;
cleanup:
if (solver) { counters(r,solver); CVodeFree(&solver); }
if (linear) SUNLinSolFree(linear);
if (matrix) SUNMatDestroy(matrix);
if (y) N_VDestroy(y);
if (atol) N_VDestroy(atol);
if (scratch) N_VDestroy(scratch);
SUNContext_Free(&ctx);
return success;
}
+119
View File
@@ -0,0 +1,119 @@
#include "runtime.h"
#include <sundials/sundials_config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
static void json_string(FILE *f, const char *value) {
fputc('"',f);
for (const unsigned char *p=(const unsigned char *)value; *p; p++) {
if (*p=='"' || *p=='\\') { fputc('\\',f); fputc(*p,f); }
else if (*p<32) fprintf(f,"\\u%04x",*p);
else fputc(*p,f);
}
fputc('"',f);
}
static void vector(FILE *f, const double *value, int count) {
fputc('[',f);
for (int i=0;i<count;i++) fprintf(f,"%s%.17g",i?",":"",value[i]);
fputc(']',f);
}
static int probe(void) {
double t, y[NSTATES], dy[NSTATES], w[NOUTPUTS];
while (scanf("%lf",&t)==1) {
for (int i=0;i<NSTATES;i++) if (scanf("%lf",&y[i])!=1) return 64;
int ok=model_eval(t,y,dy,w);
printf("{\"success\":%s",ok?"true":"false");
if (ok) { printf(",\"rhs\":"); vector(stdout,dy,NSTATES); printf(",\"outputs\":"); vector(stdout,w,NOUTPUTS); }
printf("}\n");
}
return 0;
}
static int write_result(NativeRun *r, const char *path) {
double dy[NSTATES], final[NOUTPUTS];
int final_ok=model_eval(r->final_time,r->final_state,dy,final);
size_t length=r->count*NOUTPUTS;
if (length>268435456u/sizeof(double)) return 0;
double *values=length?malloc(length*sizeof(double)):NULL;
if (length && !values) return 0;
for (size_t i=0;i<r->count;i++) {
if (!model_eval(r->times[i],r->states+i*NSTATES,dy,values+i*NOUTPUTS)) {
free(values); return 0;
}
}
FILE *f=fopen(path,"wb"); if (!f) { free(values); return 0; }
fprintf(f,"{\"success\":%s,\"status\":",!r->status?"true":"false");
json_string(f,r->status==0?"completed":r->status==1?"cancelled":"failed");
fprintf(f,",\"message\":"); json_string(f,r->message);
fprintf(f,",\"backend\":\"native-c\",\"method\":\"%s\",\"solver\":\"%s\",\"sundialsVersion\":\"%s\","
"\"simulatedUntil\":%.17g,\"solveSeconds\":%.17g,\"solveCpuSeconds\":%.17g,"
"\"nfev\":%lu,\"acceptedSteps\":%lu,\"rejectedSteps\":%lu,\"stateTransitions\":%lu,"
"\"solverStarts\":%lu,\"njev\":%lu,\"nlu\":%lu,\"maxAcceptedStep\":%.17g,\"series\":{",
r->options.bdf?"BDF":"RK45",r->options.bdf?"CVODE":"Dormand-Prince 5(4)",SUNDIALS_VERSION,
r->final_time,r->solve_seconds,r->solve_cpu_seconds,r->nfev,r->accepted,r->rejected,
r->events,r->starts,r->njev,r->nlu,r->max_accepted_step);
if (r->count) {
fprintf(f,"\"time\":[");
for (size_t i=0;i<r->count;i++) fprintf(f,"%s%.17g",i?",":"",r->times[i]);
fputc(']',f);
for (int j=0;j<NOUTPUTS;j++) {
fputc(',',f); json_string(f,model_output_keys[j]); fprintf(f,":[");
for (size_t i=0;i<r->count;i++) fprintf(f,"%s%.17g",i?",":"",values[i*NOUTPUTS+j]);
fputc(']',f);
}
}
fprintf(f,"},\"final\":{");
if (final_ok) for (int j=0;j<NOUTPUTS;j++) {
if (j) fputc(',',f);
json_string(f,model_output_keys[j]); fprintf(f,":%.17g",final[j]);
}
fprintf(f,"},\"finalState\":"); vector(f,r->final_state,NSTATES);
fprintf(f,"}\n"); int ok=!ferror(f); if (fclose(f)) ok=0;
free(values); return ok;
}
int main(int argc, char **argv) {
NativeRun r={0};
r.options=(NativeOptions){0,10,.02,.001,1e-6,300,0,1,NULL};
const char *output="result.json";
for (int i=1;i<argc;i++) {
const char *arg=argv[i];
if (!strcmp(arg,"--probe")) return probe();
if (!strcmp(arg,"--init")) {
double y[NSTATES]; if (!model_init(y)) return 2;
vector(stdout,y,NSTATES); fputc('\n',stdout); return 0;
}
if (!strcmp(arg,"--solve-only")) { r.options.record_samples=0; continue; }
if (i+1==argc) return 64;
const char *value=argv[++i];
if (!strcmp(arg,"--output")) output=value;
else if (!strcmp(arg,"--cancel-file")) r.options.cancel_path=value;
else if (!strcmp(arg,"--method")) {
if (strcmp(value,"RK45") && strcmp(value,"BDF")) return 64;
r.options.bdf=!strcmp(value,"BDF");
} else {
char *end; double number=strtod(value,&end);
if (*end || !isfinite(number)) return 64;
if (!strcmp(arg,"--start")) r.options.start=number;
else if (!strcmp(arg,"--stop")) r.options.stop=number;
else if (!strcmp(arg,"--sample-step")) r.options.sample_step=number;
else if (!strcmp(arg,"--max-step")) r.options.max_step=number;
else if (!strcmp(arg,"--rtol")) r.options.rtol=number;
else if (!strcmp(arg,"--timeout")) r.options.timeout=number;
else return 64;
}
}
if (!(r.options.stop>r.options.start && r.options.sample_step>0 && r.options.max_step>0 &&
r.options.rtol>0 && r.options.timeout>0) ||
r.options.start+r.options.sample_step==r.options.start ||
r.options.start+r.options.max_step==r.options.start ||
!isfinite(r.options.stop-r.options.start)) return 64;
if (r.options.record_samples &&
((r.options.stop-r.options.start)/r.options.sample_step>1000000 ||
((r.options.stop-r.options.start)/r.options.sample_step+1024)*(NSTATES+NOUTPUTS)*sizeof(double)>268435456)) return 64;
native_solve(&r);
int saved=write_result(&r,output);
int code=saved?(r.status==2?2:0):3;
native_run_free(&r); return code;
}
+119
View File
@@ -0,0 +1,119 @@
/* Dormand-Prince 5(4), initial step and quartic dense output.
* Adapted from SciPy's BSD-3-Clause implementation; see THIRD_PARTY_NOTICES.txt.
*/
#include "runtime.h"
#include <math.h>
#include <string.h>
static const double C[6]={0,1./5,3./10,4./5,8./9,1};
static const double A[6][6]={
{0},{1./5},{3./40,9./40},{44./45,-56./15,32./9},
{19372./6561,-25360./2187,64448./6561,-212./729},
{9017./3168,-355./33,46732./5247,49./176,-5103./18656}};
static const double B[6]={35./384,0,500./1113,125./192,-2187./6784,11./84};
static const double E[7]={-71./57600,0,71./16695,-71./1920,17253./339200,-22./525,1./40};
static const double P[7][4]={
{1,-8048581381./2820520608,8663915743./2820520608,-12715105075./11282082432},
{0,0,0,0},
{0,131558114200./32700410799,-68118460800./10900136933,87487479700./32700410799},
{0,-1754552775./470086768,14199869525./1410260304,-10690763975./1880347072},
{0,127303824393./49829197408,-318862633887./49829197408,701980252875./199316789632},
{0,-282668133./205662961,2019193451./616988883,-1453857185./822651844},
{0,40617522./29380423,-110615467./29380423,69997945./29380423}};
static double initial_step(NativeRun *s,double t,double end,const double *y,const double *f) {
double scale[NSTATES],trial[NSTATES],f1[NSTATES],d0=0,d1=0,d2=0;
for(int i=0;i<NSTATES;i++) { scale[i]=model_atol[i]+fabs(y[i])*s->options.rtol;
d0+=pow(y[i]/scale[i],2);d1+=pow(f[i]/scale[i],2); }
d0=sqrt(d0/NSTATES);d1=sqrt(d1/NSTATES);
double h0=d0<1e-5||d1<1e-5?1e-6:.01*d0/d1;h0=fmin(h0,end-t);
for(int i=0;i<NSTATES;i++) trial[i]=y[i]+h0*f[i];
if(!native_rhs(s,t+h0,trial,f1)) return NAN;
for(int i=0;i<NSTATES;i++) d2+=pow((f1[i]-f[i])/scale[i],2);
d2=sqrt(d2/NSTATES)/h0;
double h1=d1<=1e-15&&d2<=1e-15?fmax(1e-6,h0*1e-3):pow(.01/fmax(d1,d2),.2);
return fmin(fmin(100*h0,h1),fmin(end-t,s->options.max_step));
}
typedef struct { double t, h, y[NSTATES], q[NSTATES][4]; } RkDense;
static int rk_dense(void *context, double t, double *out) {
RkDense *d=context; double x=(t-d->t)/d->h;
double powers[4]={x,x*x,x*x*x,x*x*x*x};
for (int i=0;i<NSTATES;i++) {
double sum=0; for (int j=0;j<4;j++) sum+=d->q[i][j]*powers[j];
out[i]=d->y[i]+d->h*sum;
}
return 1;
}
int native_rk45(NativeRun *r) {
double y[NSTATES], f[NSTATES], t=r->options.start;
memcpy(y,r->final_state,sizeof(y));
while (t < r->options.stop) {
double boundary=model_next_break(t,r->options.stop);
double end=boundary<r->options.stop ? nextafter(boundary,-INFINITY) : boundary;
int restart=1; double h_abs=0;
while (t<end) {
if (!native_poll(r,t) || r->accepted>10000000 || r->events>10000) return 0;
if (restart) {
r->starts++;
if (!native_rhs(r,t,y,f)) return 0;
h_abs=initial_step(r,t,end,y,f);
if (!isfinite(h_abs) || h_abs<=0) return 0;
restart=0;
}
double minimum=10*fabs(nextafter(t,INFINITY)-t);
h_abs=fmax(fmin(h_abs,r->options.max_step),minimum);
double K[7][NSTATES], yn[NSTATES], next, h;
int rejected=0;
for (;;) {
if (h_abs<minimum || !native_poll(r,t)) return 0;
next=fmin(t+h_abs,end); h=next-t; h_abs=fabs(h);
memcpy(K[0],f,sizeof(f)); int valid=1;
for (int stage=1;stage<6;stage++) {
double temp[NSTATES];
for (int i=0;i<NSTATES;i++) {
double sum=0; for (int j=0;j<stage;j++) sum+=A[stage][j]*K[j][i];
temp[i]=y[i]+h*sum;
}
if (!native_rhs(r,t+C[stage]*h,temp,K[stage])) { valid=0; break; }
}
if (valid) {
for (int i=0;i<NSTATES;i++) {
double sum=0; for (int j=0;j<6;j++) sum+=B[j]*K[j][i];
yn[i]=y[i]+h*sum;
}
valid=native_rhs(r,t+h,yn,K[6]);
}
double error=0;
if (valid) {
for (int i=0;i<NSTATES;i++) {
double sum=0; for (int j=0;j<7;j++) sum+=E[j]*K[j][i];
double scale=model_atol[i]+fmax(fabs(y[i]),fabs(yn[i]))*r->options.rtol;
error+=pow(h*sum/scale,2);
}
error=sqrt(error/NSTATES);
} else error=INFINITY;
if (error<1) {
double factor=error==0?10:fmin(10,.9*pow(error,-.2));
if (rejected) factor=fmin(factor,1);
h_abs*=factor; break;
}
h_abs*=fmax(.2,.9*pow(error,-.2)); rejected=1; r->rejected++;
}
RkDense dense={0}; dense.t=t; dense.h=h; memcpy(dense.y,y,sizeof(y));
for (int i=0;i<NSTATES;i++) for (int j=0;j<4;j++)
for (int k=0;k<7;k++) dense.q[i][j]+=K[k][i]*P[k][j];
r->accepted++; r->max_accepted_step=fmax(r->max_accepted_step,h);
int impact=native_accept(r,t,next,y,yn,rk_dense,&dense,&t,y);
if (impact<0) return 0;
if (impact) restart=1;
else memcpy(f,K[6],sizeof(f));
}
t=boundary; r->final_time=t; memcpy(r->final_state,y,sizeof(y));
double sample=r->options.start+r->sample_index*r->options.sample_step;
if (r->options.record_samples && sample<=t && sample<=r->options.stop) {
if (!native_append(r,sample,y)) return 0;
r->sample_index++;
}
}
return t==r->options.stop;
}