Replace Python numerical kernels with native C execution
This commit is contained in:
1 parent
48da6be21c
commit
3b38f73fe0
227 files changed
+16801
-75499
No files matched your search
@@ -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 构建。
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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); }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user