现有组件功能与Amesim组件比较对齐,补全功能;仿真服务启动环境检测,旧版json工程文件适配读取

This commit is contained in:
ljz committed 2026-09-13 20:01:56 +08:00
1 parent b6c22a54f8
commit 6fc9afe41d
119 files changed
+260114 -351

No files matched your search

+10
View File
@@ -1,5 +1,7 @@
# C 数值后端
MECMAS21 已支持静摩擦保持与脱离、高级低速区间和 Stribeck 过渡;内部摩擦状态随接受的事件更新并保存,BDF/RK45 共用该机制。LSTP00A、MECMAS21 弹性限位和 PNL0001/2/3 的本轮语义及 Amesim 实测范围见 [功能验证报告](../docs/other/Amesim摩擦与管路功能补齐验证-2026-09-13.md)。
后端 Python 校验 XML、检查连接、生成系统专用 C 并编译;独立 EXE 执行完整数值循环。原生运行不调用 Python。当前构建支持 Windows x64 和 Linux x86_64;Linux 使用静态链接的 SUNDIALS。
平台相关修改遵循 [Windows 与 Linux 交付约定](../docs/standard/跨平台交付约定.md)。缓存时间戳更新按 `os.supports_follow_symlinks` 检测运行平台能力,Windows 不支持该可选操作时对已校验的普通缓存目录使用常规 `utime`。缓存集成回归可在两平台使用真实工具链执行:`python -m unittest tests.test_native_cache_storage tests.test_native_cache_platform -v`;设置 `SIMULATION_NATIVE_REQUIRE_TOOLCHAIN=1` 时工具链缺失将失败而不会跳过。
@@ -75,6 +77,14 @@ BDF 默认使用结构着色差分,无需环境变量或网页选项。修正
## 代码职责
后端启动后会异步执行原生环境自检,编辑器和 API 无需等待。首次检测失败后最多再重试 5 轮,共最多 6 次检测;每轮失败后等待 0.5 秒再开始下一轮,任一轮成功即停止。自检使用现有 `SIMULATION_NATIVE_CC`/PATH、`SUNDIALS_ROOT` 和默认探测方式,不写入机器专用编译器路径。它与正式构建共用编译选项和链接库,依次预处理、编译、链接并运行一个一状态 BDF 程序,核对已知解和成功标记;每轮都实际编译,不使用模型缓存。
`GET /api/simulation/runtime-check` 只读取本次启动自检的状态,返回当前阶段、轮次、结果和耗时,不触发新的检测。网页仿真控制台显示阶段变化;中间失败只提示未通过及正在重试,不展示报错详情,后端也不立即打印异常堆栈。全部检测失败后才显示最后一次的失败阶段、编译器、命令、工作目录、退出码及错误输出。进程未启动时退出码为 null;stdout/stderr 各保留最后 8000 字符。终态响应中的 `attemptHistory` 保留各轮结果、耗时和失败详情,便于追查首次故障;`durationMs` 是包括重试等待在内的总耗时,`startedAt`/`finishedAt` 使用 UTC 时间。
检测及重试期间每 0.5 秒查询,结束后停止定时查询;切回页面时读取一次结果以识别后端重启。提示明确描述“本次启动自检”,不代表持续的健康监测,也不改动仿真进度或阻断编辑器。检测通过仅表示本次工具链检查通过,不保证任意模型都能仿真成功。
Windows 使用 EXE 和与正式构建相同的 DLL 复制规则;Linux 使用现有静态库及链接分组规则。临时自检程序位于 `app/data/native-runtime-checks/`,正常完成、失败或取消后清理,避免 Linux `/tmp` 的 noexec 挂载产生误报。编译各阶段有 30 秒超时,运行有 10 秒超时;取消/超时会终止本次检测创建的进程树。既有 `SIMULATIONAPP_WARMUP=off` 仍可关闭检测,网页会明确提示已关闭。后台服务不会因普通自检失败而退出,也不会自动修改 PATH 或切换求解内核。
- `app/simulation/native_codegen/input.py`:CLI 输入适配。
- `contracts.py`:逐组件 C 实现版本白名单,新增模型或版本不会自动视为已支持。
- `compiler.py` / `extended.py`:能力检查、状态/输出布局、连接分组、常系数约束消元、C 生成。保留已验证的简单拓扑快速生成路径,两条路径均只运行 C 数值代码。
+10 -3
View File
@@ -1,6 +1,15 @@
#include "kernels.h"
#include <math.h>
double native_dry_friction(double velocity, double static_share, double coulomb,
double stiction, double stribeck_velocity, int stribeck,
double mode) {
if (fabs(mode)<.5) return -static_share;
double resistance=coulomb;
if (stribeck) resistance+=(stiction-coulomb)*exp(-3*fabs(velocity)/stribeck_velocity);
return mode>0 ? -resistance : resistance;
}
double native_contact(double penetration, double velocity, double stiffness,
double damping, double pdis, int signed_force) {
if (penetration <= 0) return 0;
@@ -21,7 +30,5 @@ void native_stop_motion(double x, double v, double lower, double upper,
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);
return native_contact(penetration, velocity, stiffness, damping, depth, signed_force);
}
+14
View File
@@ -239,3 +239,17 @@ int native_medium_gas(const NativeMedium *medium, double m, double U, double V,
int native_medium_gas_context(NativePropertyCache *cache,const NativeMedium *m,double mass,double U,double V,NativeGas *g) {
int ok=native_medium_gas(m,mass,U,V,g);if(ok)remember_gas(cache,m,g);return ok;
}
int native_polytropic_gas_context(NativePropertyCache *cache,const NativeMedium *m,
double mass,double T,double V,NativeGas *g) {
/* Amesim's polytropic storage uses ideal-gas mass even when the selected
gas supplies real-fluid enthalpy/density to the connected resistances. */
if(!(mass>0 && T>0 && V>0))return 0;
g->T=T;g->p=mass*m->R*T/V;
g->rho=native_density_context(cache,m,g->p,T);
double dt=T-m->Tref;
g->h=m->real_helium?h_ideal(T)+h_departure(g->p,T):m->cp*T+.5*m->slope*dt*dt;
g->u=g->h-g->p/g->rho;
if(!(g->p>0 && g->rho>0 && isfinite(g->p) && isfinite(g->h) && isfinite(g->u)))return 0;
remember_gas(cache,m,g);return 1;
}
+7 -3
View File
@@ -2,9 +2,11 @@
#include <math.h>
double native_signal(double t, double start, int stages, int cyclic, const double *data) {
double elapsed = fmax(t-start,0), duration = 0, offset = 0;
if (t < start) return data[0];
double elapsed = t-start, duration = 0, offset = 0;
for (int i=0;i<stages;i++) duration += data[16+i];
if (cyclic && duration > 0) elapsed = fmod(elapsed,duration);
else if (elapsed >= duration) return data[8+stages-1];
for (int i=0;i<stages;i++) {
double d = data[16+i];
if (elapsed < offset+d || i == stages-1)
@@ -18,7 +20,9 @@ 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++) {
/* A cyclic endpoint is already the next cycle's start. Listing both can
create two nearly equal floating-point break times for one event. */
for (int i=0;i<stages+(cyclic?0:1);i++) {
double event=start+offset;
if (cyclic && duration > 0 && event <= t) {
double cycle=fmax(0,floor((t-event)/duration)+1);
@@ -26,7 +30,7 @@ double native_signal_break(double t, double end, double start, int stages,
if (event <= t) event += duration;
}
if (event > t) result=fmin(result,event);
offset += data[16+i];
if (i < stages) offset += data[16+i];
}
return result;
}
+10
View File
@@ -7,6 +7,15 @@ typedef struct {
double lower, upper;
double lower_restitution, upper_restitution, lower_threshold, upper_threshold;
} NativeStop;
/* Discrete slip direction is stored with the sampled state, never in a global
* RHS cache. Trial/Jacobian evaluations cannot change sticking/sliding mode. */
typedef struct {
int velocity_index, mode_index;
double velocity_threshold, breakaway_force;
} NativeFriction;
double native_dry_friction(double velocity, double static_share, double coulomb,
double stiction, double stribeck_velocity, int stribeck,
double mode);
/* Constants are emitted per medium instance by the model compiler. */
typedef struct { int real_helium; double R, cp, Tref, slope, mu, muT, S; } NativeMedium;
/* Caller-owned scratch for ONE model_eval. Keys use exact values and a copy of
@@ -27,6 +36,7 @@ typedef struct {
void native_properties_init(NativePropertyCache *, NativePropertyState *, size_t capacity);
int native_gas_context(NativePropertyCache *, double m, double U, double V, NativeGas *);
int native_medium_gas_context(NativePropertyCache *, const NativeMedium *, double m, double U, double V, NativeGas *);
int native_polytropic_gas_context(NativePropertyCache *, const NativeMedium *, double mass, double T, double V, NativeGas *);
double native_temperature_ph_context(NativePropertyCache *, const NativeMedium *, double p, double h);
double native_density_context(NativePropertyCache *, const NativeMedium *, double p, double T);
int native_orifice_context(NativePropertyCache *, double p1, double p2, double h1, double h2,
+3
View File
@@ -2,6 +2,9 @@
#define NATIVE_RUNTIME_H
#include "model.h"
#include <stddef.h>
#ifndef NFRICTIONS
#define NFRICTIONS 0
#endif
typedef struct {
double start, stop, sample_step, max_step, rtol, timeout;
int bdf, record_samples;
+76 -3
View File
@@ -98,12 +98,32 @@ static double locate(int idx, double bound, int lower, double left, double right
return right;
}
#if NFRICTIONS
static int friction_drives(NativeRun *r,double t,const double *state,double *drives) {
r->nfev++;
return model_friction_drives(t,state,drives);
}
static double locate_breakaway(NativeRun *r,int index,double left,double right,
NativeDense dense,void *context) {
double state[NSTATES],drives[NFRICTIONS];
for(int i=0;i<60;i++) {
double mid=left+.5*(right-left);
if(mid<=left || mid>=right) break;
if(!dense(context,mid,state) || !friction_drives(r,mid,state,drives)) return NAN;
if(fabs(drives[index])>model_frictions[index].breakaway_force) right=mid;
else left=mid;
}
return right;
}
#endif
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;
double when[2*(NSTOPS+NFRICTIONS+1)], bounds[2*(NSTOPS+NFRICTIONS+1)];
double restitution[2*(NSTOPS+NFRICTIONS+1)], thresholds[2*(NSTOPS+NFRICTIONS+1)];
int indices[2*(NSTOPS+NFRICTIONS+1)], friction[2*(NSTOPS+NFRICTIONS+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);
@@ -120,12 +140,38 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
}
if (isfinite(at)) {
when[count]=at; bounds[count]=bound; indices[count]=v;
friction[count]=-1;
restitution[count]=lower?s.lower_restitution:s.upper_restitution;
thresholds[count]=lower?s.lower_threshold:s.upper_threshold;
count++;
}
}
}
#if NFRICTIONS
double before[NFRICTIONS],after[NFRICTIONS];
if(!friction_drives(r,t,old,before) || !friction_drives(r,next,trial,after)) return -1;
for(int j=0;j<NFRICTIONS;j++) {
NativeFriction f=model_frictions[j];
double mode=old[f.mode_index],at=INFINITY;
if(fabs(mode)<.5) {
if(fabs(before[j])>f.breakaway_force) at=t;
else if(fabs(after[j])>f.breakaway_force)
at=locate_breakaway(r,j,t,next,dense,context);
} else {
double direction=mode>0?1:-1;
if(direction*old[f.velocity_index]>f.velocity_threshold &&
direction*trial[f.velocity_index]<=f.velocity_threshold)
at=locate(f.velocity_index,direction*f.velocity_threshold,mode>0,t,next,dense,context);
else if(direction*old[f.velocity_index]>=0 && direction*trial[f.velocity_index]<0)
at=locate(f.velocity_index,0,mode>0,t,next,dense,context);
}
if(isnan(at)) return -1;
if(isfinite(at)) {
when[count]=at;indices[count]=f.velocity_index;friction[count]=j;
bounds[count]=restitution[count]=thresholds[count]=0;count++;
}
}
#endif
double stop=next;
for (int i=0;i<count;i++) stop=fmin(stop,when[i]);
if (r->options.record_samples) {
@@ -139,10 +185,28 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
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)) {
if(friction[i]>=0) continue;
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 NFRICTIONS
/* Stop/reverse only at an accepted event. The discrete mode travels
* with saved states so replayed results and Jacobian trials are pure. */
double drives[NFRICTIONS];
for(int i=0;i<count;i++) if(friction[i]<0 && fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
for(int j=0;j<NFRICTIONS;j++) if(model_frictions[j].velocity_index==indices[i]) {
double velocity=accepted_state[indices[i]];
accepted_state[model_frictions[j].mode_index]=velocity>0?1:(velocity<0?-1:0);
}
}
if(!friction_drives(r,stop,accepted_state,drives)) return -1;
for(int i=0;i<count;i++) if(friction[i]>=0 && fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
int j=friction[i];NativeFriction f=model_frictions[j];
accepted_state[f.velocity_index]=0;
accepted_state[f.mode_index]=fabs(drives[j])>f.breakaway_force ? (drives[j]>0?1:-1) : 0;
}
#endif
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++;
@@ -155,6 +219,15 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
int native_solve(NativeRun *r) {
double y[NSTATES];
if (!model_init(y)) { r->status=2; r->message="Native model initialization failed."; return 0; }
#if NFRICTIONS
double drives[NFRICTIONS];
if(!friction_drives(r,r->options.start,y,drives)) {r->status=2;r->message="Cannot initialize friction.";return 0;}
for(int j=0;j<NFRICTIONS;j++) {
NativeFriction f=model_frictions[j];
if(fabs(y[f.mode_index])<.5 && fabs(drives[j])>f.breakaway_force)
y[f.mode_index]=drives[j]>0?1:-1;
}
#endif
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();