优化雅可比矩阵计算;端口转发情况下仿真结果传输方式优化

This commit is contained in:
lujingze committed 2026-09-12 03:57:31 +00:00
1 parent 3bc4be3c06
commit aa4951b14e
28 files changed
+8038 -23

No files matched your search

+9 -1
View File
@@ -48,13 +48,21 @@ EXE 不需要 Python、SciPy、XML 或原工程文件。DLL 需要与 EXE 一同
`--solve-only` 关闭轨迹采样,只输出最终状态与诊断。`solveSeconds` 是程序内部数值求解墙钟时间,包含求解必需的 RHS 和事件定位,排除模型初始化、结果投影和文件写入;`processWallSeconds` 另含进程启动与结果处理。预热一次后报告三次求解的中位数。
## 雅可比分组差分与诊断
BDF 默认使用结构着色差分,无需环境变量或网页选项。修正八路的 132 个状态合并为 27 个扰动组,每次另算 1 次专用基准 RHS,共 28 次系统求值。普通积分 RHS、`rtol=1e-8` 和 Dense LU 沿用当前设置。用户已接受报告中的数值差异,正式网页与独立 C 程序采用同一默认策略。
旧 `SIMULATION_NATIVE_JACOBIAN` 选择器和 `--jacobian dense|auto|verify` 入口已删除。独立 C 程序仅保留无参数诊断标志 `--verify-jacobian`,它会逐次核对完整 canonical 雅可比矩阵,增加计算量,不用于速度测量。RK45 不构造雅可比。
无法证明结构、没有分组收益的模型继续使用必要的逐列差分;分组扰动失败时也保留 canonical 逐列恢复。这些是当前算法的兼容与恢复路径。当前启用及网页验证见 [正式启用报告](../docs/other/雅可比算法正式启用与网页验收-2026-09-11.md),机制、历史性能与误差见 [试验报告](../docs/other/雅可比结构着色试验与八路验证-2026-09-11.md)。
## 能力与限制
- 已实现当前注册的 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 求解器的雅可比策略。
- 支持原生 RK45 与 CVODE BDF。CVODE 默认按可证明的结构启用着色差分,使用稠密线性求解;不支持分组的模型自动保留逐列差分。
- 网页和 Python CLI 默认 `rtol=1e-8`;生成的状态绝对误差限为质量 `1e-14 kg`、内能 `1e-8 J`、速度/位移 `1e-12`(各自 SI 单位)。独立 C 程序默认 `rtol=1e-6`,对照时应显式传入。CLI 可覆盖 rtol;本版不支持自定义 atol 或 first_step。不同积分器相同局部容差不保证全局曲线误差完全相同。
- 时间信号显式分段,塑性/反弹端挡用稠密插值定位并重启。试探 RHS 不修改已接受状态。柔性接触沿用现有分段力公式,不改变刚度或阻尼来提速。
- 每任务独立进程,支持进度、取消及超时。进程崩溃不会作为成功返回,受控失败保留最后接受状态。
+5
View File
@@ -15,6 +15,10 @@ typedef struct {
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;
/* Structure coloring is automatic; verification is diagnostic only. */
int jacobian_verify, jacobian_colored;
unsigned long jacobian_rhs, jacobian_colored_evals, jacobian_fallbacks;
unsigned long jacobian_checks, jacobian_mismatches, cvode_rhs, linear_rhs;
int status; /* 0 completed, 1 cancelled, 2 failed */
const char *message;
} NativeRun;
@@ -23,6 +27,7 @@ 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_jacobian_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,
+13
View File
@@ -54,6 +54,19 @@ int native_rhs(NativeRun *r, double t, const double *y, double *dy) {
return model_eval(t,y,dy,w);
}
/* Derivative probes use deterministic property evaluation: the generated
* Jacobian entry point omits cross-storage cache seeds, while the ordinary RHS
* retains all existing physical expressions and state-property reuse. */
int native_jacobian_rhs(NativeRun *r, double t, const double *y, double *dy) {
#if defined(MODEL_JACOBIAN_CANONICAL_RHS) && MODEL_JACOBIAN_CANONICAL_RHS
double w[NOUTPUTS];
r->nfev++;
return model_eval_jacobian(t,y,dy,w);
#else
return native_rhs(r,t,y,dy);
#endif
}
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;
+186 -4
View File
@@ -1,5 +1,7 @@
/* CVODE owns its default numerical Jacobian and dense linear solver.
* No project Jacobian, sparsity or derivative policy is changed here.
/* CVODE retains its Jacobian refresh policy and dense matrix/LU solver.
* A compiler-proven sparsity pattern groups independent finite differences;
* unsupported models and patterns without grouping benefit retain CVODE's
* default callback.
*/
#include "runtime.h"
#include <cvode/cvode.h>
@@ -9,6 +11,10 @@
#include <sunlinsol/sunlinsol_dense.h>
#include <math.h>
#include <string.h>
#include <sundials/sundials_math.h>
#ifndef MODEL_JACOBIAN_COLORED
#define MODEL_JACOBIAN_COLORED 0
#endif
typedef struct { void *solver; N_Vector scratch; } CvDense;
static int cv_dense(void *context, double t, double *out) {
@@ -17,16 +23,176 @@ static int cv_dense(void *context, double t, double *out) {
memcpy(out,N_VGetArrayPointer(d->scratch),NSTATES*sizeof(double));
return 1;
}
typedef struct {
NativeRun *run;
void *solver;
N_Vector weights;
SUNMatrix reference;
int colored;
} CvContext;
static int cv_rhs(sunrealtype t, N_Vector y, N_Vector dy, void *context) {
NativeRun *r=context;
NativeRun *r=((CvContext *)context)->run;
if (!native_poll(r,r->final_time)) return -1;
return native_rhs(r,t,N_VGetArrayPointer(y),N_VGetArrayPointer(dy)) ? 0 : 1;
}
#if MODEL_JACOBIAN_COLORED
/* Check the generated CSC bounds and coloring once, before installing it. */
static int valid_coloring(void) {
if (MODEL_JACOBIAN_COLOR_COUNT<=0 || MODEL_JACOBIAN_COLOR_COUNT>=NSTATES ||
model_jacobian_col_ptr[0]!=0 || model_jacobian_col_ptr[NSTATES]!=MODEL_JACOBIAN_NNZ) return 0;
for (int j=0;j<NSTATES;j++) {
if (model_jacobian_column_color[j]<0 || model_jacobian_column_color[j]>=MODEL_JACOBIAN_COLOR_COUNT ||
model_jacobian_col_ptr[j]<0 || model_jacobian_col_ptr[j+1]<model_jacobian_col_ptr[j] ||
model_jacobian_col_ptr[j+1]>MODEL_JACOBIAN_NNZ) return 0;
int previous=-1;
for (int k=model_jacobian_col_ptr[j];k<model_jacobian_col_ptr[j+1];k++) {
int row=model_jacobian_row_index[k];
if (row<=previous || row>=NSTATES) return 0;
previous=row;
}
}
for (int color=0;color<MODEL_JACOBIAN_COLOR_COUNT;color++) {
unsigned char used[NSTATES]={0};
for (int j=0;j<NSTATES;j++) if (model_jacobian_column_color[j]==color) {
for (int k=model_jacobian_col_ptr[j];k<model_jacobian_col_ptr[j+1];k++) {
int row=model_jacobian_row_index[k];
if (used[row]) return 0;
used[row]=1;
}
}
}
return 1;
}
static int jac_rhs(CvContext *context, sunrealtype t, N_Vector y, N_Vector f) {
if (!native_poll(context->run,context->run->final_time)) return -1;
context->run->jacobian_rhs++;
#if defined(MODEL_JACOBIAN_CANONICAL_RHS) && MODEL_JACOBIAN_CANONICAL_RHS
return native_jacobian_rhs(context->run,t,N_VGetArrayPointer(y),N_VGetArrayPointer(f)) ? 0 : 1;
#else
return native_rhs(context->run,t,N_VGetArrayPointer(y),N_VGetArrayPointer(f)) ? 0 : 1;
#endif
}
/* Same perturbation and reciprocal-multiply order as SUNDIALS 7.4 dense DQ.
* No constraints are set by this runtime. If that changes, carry their vector
* into this context and apply CVODE's sign rule before enabling coloring. */
static int jac_increments(CvContext *context, N_Vector y, N_Vector fy, double *increments) {
sunrealtype step;
if (CVodeGetErrWeights(context->solver,context->weights)<0 ||
CVodeGetCurrentStep(context->solver,&step)<0) return -1;
double norm=N_VWrmsNorm(fy,context->weights);
double minimum=norm!=0 ? 1000.0*fabs(step)*SUN_UNIT_ROUNDOFF*NSTATES*norm : 1.0;
double *state=N_VGetArrayPointer(y), *weight=N_VGetArrayPointer(context->weights);
double square_root=sqrt(SUN_UNIT_ROUNDOFF);
for (int j=0;j<NSTATES;j++) {
increments[j]=fmax(square_root*fabs(state[j]),minimum/weight[j]);
if (!(increments[j]>0) || !isfinite(increments[j])) return 1;
}
return 0;
}
static int dense_difference(CvContext *context, sunrealtype t, N_Vector y, N_Vector fy,
SUNMatrix matrix, N_Vector trial, N_Vector ftrial, const double *increments) {
double *state=N_VGetArrayPointer(y), *test=N_VGetArrayPointer(trial);
double *base=N_VGetArrayPointer(fy), *value=N_VGetArrayPointer(ftrial);
memcpy(test,state,NSTATES*sizeof(double));
for (int j=0;j<NSTATES;j++) {
test[j]=state[j]+increments[j];
int flag=jac_rhs(context,t,trial,ftrial);
test[j]=state[j];
if (flag) return flag;
double inverse=1.0/increments[j];
double *column=SUNDenseMatrix_Column(matrix,j);
for (int i=0;i<NSTATES;i++) column[i]=inverse*(value[i]-base[i]);
}
return 0;
}
static int colored_difference(CvContext *context, sunrealtype t, N_Vector y, N_Vector fy,
SUNMatrix matrix, N_Vector trial, N_Vector ftrial, const double *increments) {
double *state=N_VGetArrayPointer(y), *test=N_VGetArrayPointer(trial);
double *base=N_VGetArrayPointer(fy), *value=N_VGetArrayPointer(ftrial);
if (SUNMatZero(matrix)) return -1;
for (int color=0;color<MODEL_JACOBIAN_COLOR_COUNT;color++) {
memcpy(test,state,NSTATES*sizeof(double));
for (int j=0;j<NSTATES;j++) if (model_jacobian_column_color[j]==color) test[j]+=increments[j];
int flag=jac_rhs(context,t,trial,ftrial);
if (flag) return flag;
for (int j=0;j<NSTATES;j++) if (model_jacobian_column_color[j]==color) {
double inverse=1.0/increments[j];
double *column=SUNDenseMatrix_Column(matrix,j);
for (int k=model_jacobian_col_ptr[j];k<model_jacobian_col_ptr[j+1];k++) {
int i=model_jacobian_row_index[k];
column[i]=inverse*(value[i]-base[i]);
}
}
}
context->run->jacobian_colored_evals++;
return 0;
}
static int cv_jacobian(sunrealtype t, N_Vector y, N_Vector fy, SUNMatrix matrix,
void *user, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) {
CvContext *context=user;
NativeRun *r=context->run;
double increments[NSTATES];
(void)tmp3;
int flag=jac_increments(context,y,fy,increments);
if (flag) return flag;
#if defined(MODEL_JACOBIAN_CANONICAL_RHS) && MODEL_JACOBIAN_CANONICAL_RHS
/* The CVODE fy belongs to the ordinary RHS cache path. Recompute the
baseline using the same deterministic path as every perturbed probe;
mixing the two baselines would amplify cache roundoff by 1/increment. */
flag=jac_rhs(context,t,y,tmp3);
if (flag) return flag;
fy=tmp3;
#endif
if (!context->colored) return dense_difference(context,t,y,fy,matrix,tmp2,tmp1,increments);
flag=colored_difference(context,t,y,fy,matrix,tmp2,tmp1,increments);
if (flag<0) return flag; /* cancellation/timeout must not trigger retries */
if (flag>0) {
r->jacobian_fallbacks++;
return dense_difference(context,t,y,fy,matrix,tmp2,tmp1,increments);
}
if (context->reference) {
/* Diagnostic mode validates every entry, not a sampled submatrix. */
flag=dense_difference(context,t,y,fy,context->reference,tmp2,tmp1,increments);
if (flag) return flag;
r->jacobian_checks++;
int mismatch=0;
for (int j=0;j<NSTATES;j++) for (int i=0;i<NSTATES;i++)
if (SM_ELEMENT_D(matrix,i,j)!=SM_ELEMENT_D(context->reference,i,j)) mismatch=1;
if (mismatch) {
fprintf(stderr,"{\"event\":\"jacobian-verification-mismatch\",\"time\":%.17g,\"entries\":[",(double)t);
int written=0;
for (int j=0;j<NSTATES;j++) for (int i=0;i<NSTATES;i++) {
double actual=SM_ELEMENT_D(matrix,i,j), expected=SM_ELEMENT_D(context->reference,i,j);
if (actual!=expected && written<12) {
fprintf(stderr,"%s{\"row\":%d,\"column\":%d,\"colored\":%.17g,\"dense\":%.17g}",written?",":"",i,j,actual,expected);
written++;
}
}
fprintf(stderr,"],\"state\":[");
for (int i=0;i<NSTATES;i++) fprintf(stderr,"%s%.17g",i?",":"",N_VGetArrayPointer(y)[i]);
fprintf(stderr,"]}\n");
r->jacobian_mismatches++; r->jacobian_fallbacks++;
context->colored=0; r->jacobian_colored=0;
if (SUNMatCopy(context->reference,matrix)) return -1;
}
}
return 0;
}
#endif
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;
CVodeGetNumRhsEvals(solver,&value); r->cvode_rhs+=(unsigned long)value;
CVodeGetNumLinRhsEvals(solver,&value); r->linear_rhs+=(unsigned long)value;
}
int native_bdf(NativeRun *r) {
@@ -35,6 +201,7 @@ int native_bdf(NativeRun *r) {
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;
CvContext context={.run=r};
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));
@@ -44,11 +211,24 @@ int native_bdf(NativeRun *r) {
if (!linear) goto cleanup;
solver=CVodeCreate(CV_BDF,ctx);
if (!solver) goto cleanup;
context.solver=solver;
double t=r->options.start;
if (CVodeInit(solver,cv_rhs,t,y)<0 || CVodeSetUserData(solver,r)<0 ||
if (CVodeInit(solver,cv_rhs,t,y)<0 || CVodeSetUserData(solver,&context)<0 ||
CVodeSVtolerances(solver,r->options.rtol,atol)<0 ||
CVodeSetLinearSolver(solver,linear,matrix)<0 ||
CVodeSetMaxStep(solver,r->options.max_step)<0) goto cleanup;
#if MODEL_JACOBIAN_COLORED
if (valid_coloring()) {
context.weights=N_VClone(y);
if (!context.weights) goto cleanup;
if (r->jacobian_verify) {
context.reference=SUNDenseMatrix(NSTATES,NSTATES,ctx);
if (!context.reference) goto cleanup;
}
context.colored=1; r->jacobian_colored=1;
if (CVodeSetJacFn(solver,cv_jacobian)<0) goto cleanup;
}
#endif
r->starts++;
CvDense dense={solver,scratch};
while (t<r->options.stop) {
@@ -87,6 +267,8 @@ int native_bdf(NativeRun *r) {
success=1;
cleanup:
if (solver) { counters(r,solver); CVodeFree(&solver); }
if (context.reference) SUNMatDestroy(context.reference);
if (context.weights) N_VDestroy(context.weights);
if (linear) SUNLinSolFree(linear);
if (matrix) SUNMatDestroy(matrix);
if (y) N_VDestroy(y);
+8
View File
@@ -59,6 +59,13 @@ static int write_result(NativeRun *r, const char *path, const char *index_path)
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,",\"jacobianMode\":\"%s\",\"jacobianRhsCalls\":%lu,"
"\"jacobianColoredEvals\":%lu,\"jacobianFallbacks\":%lu,"
"\"jacobianChecks\":%lu,\"jacobianMismatches\":%lu,"
"\"cvodeRhsCalls\":%lu,\"cvodeLinearRhsCalls\":%lu",
!r->options.bdf?"not-used":r->jacobian_colored?"colored-difference":"dense-difference",
r->jacobian_rhs,r->jacobian_colored_evals,r->jacobian_fallbacks,
r->jacobian_checks,r->jacobian_mismatches,r->cvode_rhs,r->linear_rhs);
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,"
@@ -116,6 +123,7 @@ int main(int argc, char **argv) {
vector(stdout,y,NSTATES); fputc('\n',stdout); return 0;
}
if (!strcmp(arg,"--solve-only")) { r.options.record_samples=0; continue; }
if (!strcmp(arg,"--verify-jacobian")) { r.jacobian_verify=1; continue; }
if (i+1==argc) return 64;
const char *value=argv[++i];
if (!strcmp(arg,"--output")) output=value;