393 lines
22 KiB
Python
393 lines
22 KiB
Python
"""Small standalone checks of the production colored CVODE Jacobian callback.
|
|
|
|
The independent oracle is the actual SUNDIALS 7.4 cvLsDenseDQJac symbol in the
|
|
installed static library for the unchanged cache path. Canonical-mode tests
|
|
separately verify a recomputed baseline and the original-fy perturbation policy;
|
|
that derivative is intentionally not equated to the old shared-cache DQ.
|
|
Its private headers are used only by this test TU,
|
|
never by production code. No full application/model compilation is required.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from app.simulation.native_codegen.build import LIBRARIES, toolchain
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / "test/solver-newton-20260911/toolchain/sundials-7.4.0"
|
|
MODEL_HEADER = r'''
|
|
#ifndef TEST_MODEL_H
|
|
#define TEST_MODEL_H
|
|
#define NSTATES 4
|
|
#define NOUTPUTS 4
|
|
#define MODEL_JACOBIAN_COLORED 1
|
|
#define MODEL_JACOBIAN_COLOR_COUNT 2
|
|
#define MODEL_JACOBIAN_NNZ 8
|
|
extern int model_jacobian_col_ptr[5], model_jacobian_row_index[8], model_jacobian_column_color[4];
|
|
extern const double model_atol[4];
|
|
double model_next_break(double time,double end);
|
|
#endif
|
|
'''
|
|
HARNESS = r'''
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "cvode_impl.h"
|
|
#include "cvode_ls_impl.h"
|
|
#include "@RUNTIME@"
|
|
#if SUNDIALS_VERSION_MAJOR != 7 || SUNDIALS_VERSION_MINOR != 4 || SUNDIALS_VERSION_PATCH != 0
|
|
#error This private-ABI test must use SUNDIALS 7.4.0
|
|
#endif
|
|
int model_jacobian_col_ptr[5]={0,2,4,6,8};
|
|
int model_jacobian_row_index[8]={0,1,0,1,2,3,2,3};
|
|
int model_jacobian_column_color[4]={0,1,0,1};
|
|
const double model_atol[4]={1e-9,1e-9,1e-9,1e-9};
|
|
static int assertions, zero_model, extra_dependency, reject_mode, poll_calls, cancel_poll;
|
|
static int break_enabled, event_enabled, event_done;
|
|
static double baseline_state[NSTATES];
|
|
static int canonical_trace, canonical_count, reject_canonical_base;
|
|
static double canonical_inputs[16][NSTATES];
|
|
#define CHECK(condition) do { assertions++; if(!(condition)){fprintf(stderr,"check failed at line %d: %s\n",__LINE__,#condition);exit(1);} } while(0)
|
|
static int evaluate(const double *y,double *f) {
|
|
int perturbed=0;
|
|
for(int i=0;i<NSTATES;i++)perturbed+=y[i]!=baseline_state[i];
|
|
if((reject_mode==1 && perturbed>1) || (reject_mode==2 && perturbed>0))return 0;
|
|
if(zero_model){for(int i=0;i<NSTATES;i++)f[i]=0;return 1;}
|
|
for(int i=0;i<NSTATES;i+=2) {
|
|
f[i]=y[i]*y[i]+0.5*y[i+1];
|
|
f[i+1]=sin(y[i+1])+0.25*y[i];
|
|
}
|
|
if(extra_dependency)f[0]+=y[2];
|
|
return 1;
|
|
}
|
|
int native_poll(NativeRun *r,double time) {
|
|
(void)time; poll_calls++;
|
|
if(cancel_poll && poll_calls>=cancel_poll){r->status=1;return 0;}
|
|
return 1;
|
|
}
|
|
int native_rhs(NativeRun *r,double time,const double *state,double *derivative) {
|
|
(void)time;r->nfev++;return evaluate(state,derivative);
|
|
}
|
|
int native_jacobian_rhs(NativeRun *r,double time,const double *state,double *derivative) {
|
|
(void)time;r->nfev++;
|
|
if(canonical_trace) {
|
|
CHECK(canonical_count<16);
|
|
memcpy(canonical_inputs[canonical_count++],state,NSTATES*sizeof(double));
|
|
}
|
|
if(reject_canonical_base && !memcmp(state,baseline_state,sizeof(baseline_state)))return 0;
|
|
return evaluate(state,derivative);
|
|
}
|
|
int native_append(NativeRun *r,double time,const double *state) {
|
|
r->final_time=time;memcpy(r->final_state,state,NSTATES*sizeof(double));return 1;
|
|
}
|
|
int native_accept(NativeRun *r,double time,double next,const double *old,const double *trial,
|
|
NativeDense dense,void *context,double *accepted_time,double *accepted_state) {
|
|
(void)time;(void)old;(void)dense;(void)context;
|
|
*accepted_time=next;memcpy(accepted_state,trial,NSTATES*sizeof(double));
|
|
int impact=event_enabled && !event_done && next>=0.0005;
|
|
if(impact){event_done=1;accepted_state[0]=-accepted_state[0];r->events++;}
|
|
r->final_time=next;memcpy(r->final_state,accepted_state,NSTATES*sizeof(double));return impact;
|
|
}
|
|
double model_next_break(double time,double end) {return break_enabled && time<0.001 && end>0.001?0.001:end;}
|
|
typedef struct {
|
|
NativeRun run;
|
|
CvContext context;
|
|
SUNContext sun;
|
|
SUNLinearSolver linear;
|
|
N_Vector y,fy,trial,ftrial,tmp3,oracle_state;
|
|
SUNMatrix matrix,oracle;
|
|
} Fixture;
|
|
static void fixture_create(Fixture *f) {
|
|
memset(f,0,sizeof(*f));CHECK(!SUNContext_Create(SUN_COMM_NULL,&f->sun));
|
|
f->y=N_VNew_Serial(NSTATES,f->sun);f->fy=N_VClone(f->y);f->trial=N_VClone(f->y);
|
|
f->ftrial=N_VClone(f->y);f->tmp3=N_VClone(f->y);f->oracle_state=N_VClone(f->y);
|
|
f->matrix=SUNDenseMatrix(NSTATES,NSTATES,f->sun);f->oracle=SUNDenseMatrix(NSTATES,NSTATES,f->sun);
|
|
f->linear=SUNLinSol_Dense(f->y,f->matrix,f->sun);
|
|
f->context=(CvContext){.run=&f->run,.solver=CVodeCreate(CV_BDF,f->sun),.weights=N_VClone(f->y),.colored=1};
|
|
CHECK(f->y && f->fy && f->trial && f->ftrial && f->tmp3 && f->oracle_state && f->matrix && f->oracle && f->linear && f->context.solver && f->context.weights);
|
|
N_VConst(1,f->y);
|
|
CHECK(!CVodeInit(f->context.solver,cv_rhs,0,f->y));
|
|
CHECK(!CVodeSetUserData(f->context.solver,&f->context));
|
|
CHECK(!CVodeSStolerances(f->context.solver,1e-8,1e-9));
|
|
CHECK(!CVodeSetLinearSolver(f->context.solver,f->linear,f->matrix));
|
|
f->run.jacobian_colored=1;
|
|
}
|
|
static void fixture_set(Fixture *f,const double *state,const double *weights,double step) {
|
|
memcpy(N_VGetArrayPointer(f->y),state,NSTATES*sizeof(double));
|
|
memcpy(baseline_state,state,sizeof(baseline_state));CHECK(evaluate(state,N_VGetArrayPointer(f->fy)));
|
|
/* Set the actual library's trial-step state, not a second formula oracle. */
|
|
CVodeMem memory=(CVodeMem)f->context.solver;
|
|
memory->cv_h=step;memory->cv_next_h=step;
|
|
memcpy(N_VGetArrayPointer(memory->cv_ewt),weights,NSTATES*sizeof(double));
|
|
}
|
|
static void fixture_free(Fixture *f) {
|
|
CVodeFree(&f->context.solver);SUNLinSolFree(f->linear);
|
|
if(f->context.reference)SUNMatDestroy(f->context.reference);
|
|
SUNMatDestroy(f->matrix);SUNMatDestroy(f->oracle);N_VDestroy(f->context.weights);
|
|
N_VDestroy(f->y);N_VDestroy(f->fy);N_VDestroy(f->trial);N_VDestroy(f->ftrial);N_VDestroy(f->tmp3);N_VDestroy(f->oracle_state);
|
|
SUNContext_Free(&f->sun);
|
|
}
|
|
static int callback(Fixture *f) {
|
|
return cv_jacobian(0,f->y,f->fy,f->matrix,&f->context,f->ftrial,f->trial,f->tmp3);
|
|
}
|
|
static void actual_upstream_oracle(Fixture *f) {
|
|
memcpy(N_VGetArrayPointer(f->oracle_state),N_VGetArrayPointer(f->y),NSTATES*sizeof(double));
|
|
CHECK(!cvLsDenseDQJac(0,f->oracle_state,f->fy,f->oracle,(CVodeMem)f->context.solver,f->ftrial));
|
|
CHECK(!memcmp(N_VGetArrayPointer(f->oracle_state),N_VGetArrayPointer(f->y),NSTATES*sizeof(double)));
|
|
}
|
|
static void matrices_equal(Fixture *f) {
|
|
for(int j=0;j<NSTATES;j++)for(int i=0;i<NSTATES;i++) {
|
|
double a=SM_ELEMENT_D(f->matrix,i,j),b=SM_ELEMENT_D(f->oracle,i,j);
|
|
if(memcmp(&a,&b,sizeof(double)))fprintf(stderr,"matrix mismatch (%d,%d): %.17g vs %.17g\n",i,j,a,b);
|
|
CHECK(!memcmp(&a,&b,sizeof(double)));
|
|
}
|
|
}
|
|
static void formula_oracle(void) {
|
|
Fixture f;fixture_create(&f);
|
|
const double states[][4]={{0,1e-8,-2,1e4},{.1,-.3,1.25,-4.25},{-1e-20,0,2e-10,3}};
|
|
const double weights[][4]={{1e12,1e8,1e-4,1e-10},{2,.5,4,1},{1e-3,1e10,1e4,1}};
|
|
const double steps[]={1e-12,1e-3,-3e-4,2e5};
|
|
for(int kind=0;kind<2;kind++)for(int state=0;state<3;state++)for(int step=0;step<4;step++) {
|
|
zero_model=kind;fixture_set(&f,states[state],weights[state],steps[step]);
|
|
double saved_y[NSTATES],saved_f[NSTATES];
|
|
memcpy(saved_y,N_VGetArrayPointer(f.y),sizeof(saved_y));memcpy(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f));
|
|
unsigned long before=f.run.jacobian_rhs;
|
|
CHECK(!callback(&f));CHECK(f.run.jacobian_rhs-before==MODEL_JACOBIAN_COLOR_COUNT);
|
|
CHECK(!memcmp(saved_y,N_VGetArrayPointer(f.y),sizeof(saved_y)));CHECK(!memcmp(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f)));
|
|
actual_upstream_oracle(&f);matrices_equal(&f);
|
|
}
|
|
zero_model=0;
|
|
const double round_state[]={.1,.1,.1,.1},round_weights[]={1e6,1e6,1e6,1e6};double inc[NSTATES];
|
|
fixture_set(&f,round_state,round_weights,1e-12);CHECK(!jac_increments(&f.context,f.y,f.fy,inc));
|
|
CHECK((round_state[0]+inc[0])-round_state[0]!=inc[0]);
|
|
CHECK(!callback(&f));actual_upstream_oracle(&f);matrices_equal(&f);
|
|
fixture_free(&f);
|
|
}
|
|
static void coloring_validation(void) {
|
|
CHECK(valid_coloring());
|
|
model_jacobian_col_ptr[0]=1;CHECK(!valid_coloring());model_jacobian_col_ptr[0]=0;
|
|
model_jacobian_col_ptr[2]=9;CHECK(!valid_coloring());model_jacobian_col_ptr[2]=4;
|
|
model_jacobian_row_index[1]=0;CHECK(!valid_coloring());model_jacobian_row_index[1]=1;
|
|
model_jacobian_row_index[1]=NSTATES;CHECK(!valid_coloring());model_jacobian_row_index[1]=1;
|
|
model_jacobian_row_index[0]=-1;CHECK(!valid_coloring());model_jacobian_row_index[0]=0;
|
|
model_jacobian_column_color[1]=0;CHECK(!valid_coloring());model_jacobian_column_color[1]=1;
|
|
model_jacobian_column_color[1]=2;CHECK(!valid_coloring());model_jacobian_column_color[1]=1;
|
|
CHECK(valid_coloring());
|
|
}
|
|
static void recoverable_failure(int mode) {
|
|
Fixture f;fixture_create(&f);const double state[]={1.2,2.1,3.3,4.4},weight[]={1,1,1,1};
|
|
fixture_set(&f,state,weight,.001);double saved_f[NSTATES];memcpy(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f));
|
|
reject_mode=mode;int code=callback(&f);CHECK(code==(mode==1?0:1));
|
|
CHECK(f.run.jacobian_fallbacks==1);CHECK(f.run.jacobian_colored_evals==0);
|
|
CHECK(f.run.jacobian_rhs==(unsigned long)(mode==1?1+NSTATES:2));CHECK(f.run.nfev==f.run.jacobian_rhs);
|
|
CHECK(!memcmp(state,N_VGetArrayPointer(f.y),sizeof(state)));CHECK(!memcmp(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f)));
|
|
CHECK(!memcmp(state,N_VGetArrayPointer(f.trial),sizeof(state)));
|
|
reject_mode=0;if(mode==1){actual_upstream_oracle(&f);matrices_equal(&f);}fixture_free(&f);
|
|
}
|
|
static void cancellation(void) {
|
|
for(int after=1;after<=2;after++) {
|
|
Fixture f;fixture_create(&f);const double state[]={1.2,2.1,3.3,4.4},weight[]={1,1,1,1};
|
|
fixture_set(&f,state,weight,.001);double saved_f[NSTATES];memcpy(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f));
|
|
cancel_poll=after;poll_calls=0;CHECK(callback(&f)<0);
|
|
CHECK(f.run.jacobian_fallbacks==0);CHECK(f.run.jacobian_rhs==(unsigned long)(after-1));CHECK(f.run.nfev==f.run.jacobian_rhs);
|
|
CHECK(f.run.status==1);CHECK(!memcmp(state,N_VGetArrayPointer(f.y),sizeof(state)));CHECK(!memcmp(saved_f,N_VGetArrayPointer(f.fy),sizeof(saved_f)));
|
|
cancel_poll=0;fixture_free(&f);
|
|
}
|
|
}
|
|
static void verify_missing_dependency(void) {
|
|
Fixture f;fixture_create(&f);const double state[]={1.2,2.1,3.3,4.4},weight[]={1,1,1,1};
|
|
extra_dependency=1;fixture_set(&f,state,weight,.001);
|
|
f.context.reference=SUNDenseMatrix(NSTATES,NSTATES,f.sun);CHECK(f.context.reference!=NULL);CHECK(valid_coloring());
|
|
CHECK(!callback(&f));CHECK(f.run.jacobian_rhs==MODEL_JACOBIAN_COLOR_COUNT+NSTATES);
|
|
CHECK(f.run.jacobian_checks==1);CHECK(f.run.jacobian_mismatches==1);CHECK(f.run.jacobian_fallbacks==1);
|
|
CHECK(!f.context.colored && !f.run.jacobian_colored);actual_upstream_oracle(&f);matrices_equal(&f);
|
|
CHECK(!CVodeReInit(f.context.solver,.1,f.y));
|
|
const double fresh_weights[]={7,11,13,17};fixture_set(&f,state,fresh_weights,1e-5);
|
|
unsigned long before=f.run.jacobian_rhs;
|
|
CHECK(!callback(&f));CHECK(f.run.jacobian_rhs-before==NSTATES);
|
|
CHECK(f.run.jacobian_checks==1 && f.run.jacobian_mismatches==1 && f.run.jacobian_fallbacks==1);
|
|
CHECK(!f.context.colored);actual_upstream_oracle(&f);matrices_equal(&f);
|
|
fixture_free(&f);
|
|
}
|
|
static NativeRun integration_run(int verify) {
|
|
NativeRun run={0};run.jacobian_verify=verify;
|
|
run.options=(NativeOptions){0,.002,.0001,.0002,1e-8,10,1,0,NULL};
|
|
for(int i=0;i<NSTATES;i++)run.final_state[i]=.1*(i+1);
|
|
memcpy(baseline_state,run.final_state,sizeof(baseline_state));event_done=0;
|
|
CHECK(native_bdf(&run));CHECK(run.starts==3);CHECK(run.events==1);
|
|
CHECK(run.nfev==run.cvode_rhs+run.linear_rhs+run.jacobian_rhs);
|
|
CHECK(run.njev>0 && run.nlu>0 && run.accepted>0);
|
|
return run;
|
|
}
|
|
static void integration_restart_counters(void) {
|
|
break_enabled=1;event_enabled=1;
|
|
/* No user mode selects default DQ. Invalid structural metadata must still
|
|
fall back safely, including when diagnostic verification is requested. */
|
|
model_jacobian_column_color[1]=0;CHECK(!valid_coloring());
|
|
NativeRun fallback=integration_run(0),fallback_verify=integration_run(1);
|
|
model_jacobian_column_color[1]=1;CHECK(valid_coloring());
|
|
NativeRun colored=integration_run(0),verify=integration_run(1);
|
|
CHECK(!fallback.jacobian_colored && !fallback_verify.jacobian_colored);
|
|
CHECK(fallback.jacobian_rhs==0 && fallback.linear_rhs==fallback.njev*NSTATES);
|
|
CHECK(fallback_verify.jacobian_rhs==0 && fallback_verify.linear_rhs==fallback_verify.njev*NSTATES);
|
|
CHECK(fallback_verify.jacobian_checks==0);
|
|
CHECK(colored.jacobian_colored && verify.jacobian_colored);
|
|
CHECK(colored.linear_rhs==0 && colored.jacobian_rhs==colored.njev*MODEL_JACOBIAN_COLOR_COUNT);
|
|
CHECK(colored.jacobian_checks==0);
|
|
CHECK(verify.linear_rhs==0 && verify.jacobian_rhs==verify.njev*(MODEL_JACOBIAN_COLOR_COUNT+NSTATES));
|
|
CHECK(verify.jacobian_checks==verify.njev && verify.jacobian_mismatches==0);
|
|
CHECK(fallback.accepted==colored.accepted && fallback.rejected==colored.rejected && fallback.njev==colored.njev && fallback.nlu==colored.nlu);
|
|
CHECK(fallback.accepted==verify.accepted && fallback.rejected==verify.rejected && fallback.njev==verify.njev && fallback.nlu==verify.nlu);
|
|
CHECK(!memcmp(fallback.final_state,colored.final_state,sizeof(fallback.final_state)));
|
|
CHECK(!memcmp(fallback.final_state,verify.final_state,sizeof(fallback.final_state)));
|
|
CHECK(!memcmp(fallback.final_state,fallback_verify.final_state,sizeof(fallback.final_state)));
|
|
}
|
|
|
|
#if defined(MODEL_JACOBIAN_CANONICAL_RHS) && MODEL_JACOBIAN_CANONICAL_RHS
|
|
static void canonical_expected(Fixture *f,const double *increments) {
|
|
double state[NSTATES],base[NSTATES],probe[NSTATES];
|
|
memcpy(state,N_VGetArrayPointer(f->y),sizeof(state));CHECK(evaluate(state,base));
|
|
for(int j=0;j<NSTATES;j++) {
|
|
double saved=state[j];state[j]+=increments[j];CHECK(evaluate(state,probe));state[j]=saved;
|
|
for(int i=0;i<NSTATES;i++)SM_ELEMENT_D(f->oracle,i,j)=(1.0/increments[j])*(probe[i]-base[i]);
|
|
}
|
|
}
|
|
static void canonical_baseline(void) {
|
|
Fixture f;fixture_create(&f);
|
|
const double state[]={.1,.2,.3,.4},weight[]={1,2,3,4};
|
|
const double legacy_offset[]={5e5,-7e4,9e3,-1e2};
|
|
fixture_set(&f,state,weight,1e5);
|
|
double original_fy[NSTATES],canonical_fy[NSTATES],increments[NSTATES],wrong_increments[NSTATES];
|
|
memcpy(canonical_fy,N_VGetArrayPointer(f.fy),sizeof(canonical_fy));
|
|
for(int i=0;i<NSTATES;i++)N_VGetArrayPointer(f.fy)[i]+=legacy_offset[i];
|
|
memcpy(original_fy,N_VGetArrayPointer(f.fy),sizeof(original_fy));
|
|
CHECK(!jac_increments(&f.context,f.y,f.fy,increments));
|
|
memcpy(N_VGetArrayPointer(f.tmp3),canonical_fy,sizeof(canonical_fy));
|
|
CHECK(!jac_increments(&f.context,f.y,f.tmp3,wrong_increments));
|
|
CHECK(memcmp(increments,wrong_increments,sizeof(increments))!=0);
|
|
canonical_expected(&f,increments);
|
|
canonical_trace=1;canonical_count=0;
|
|
CHECK(!callback(&f));
|
|
CHECK(canonical_count==1+MODEL_JACOBIAN_COLOR_COUNT);
|
|
CHECK(f.run.jacobian_rhs==(unsigned long)canonical_count && f.run.nfev==f.run.jacobian_rhs);
|
|
CHECK(!memcmp(canonical_inputs[0],state,sizeof(state)));
|
|
for(int color=0;color<MODEL_JACOBIAN_COLOR_COUNT;color++)for(int j=0;j<NSTATES;j++) {
|
|
double expected=state[j]+(model_jacobian_column_color[j]==color?increments[j]:0);
|
|
CHECK(canonical_inputs[1+color][j]==expected);
|
|
}
|
|
CHECK(!memcmp(state,N_VGetArrayPointer(f.y),sizeof(state)));
|
|
CHECK(!memcmp(original_fy,N_VGetArrayPointer(f.fy),sizeof(original_fy)));
|
|
matrices_equal(&f);
|
|
/* Verify and disabled-colored paths must share the canonical baseline too. */
|
|
f.context.reference=SUNDenseMatrix(NSTATES,NSTATES,f.sun);CHECK(f.context.reference!=NULL);
|
|
canonical_count=0;unsigned long before=f.run.jacobian_rhs;
|
|
CHECK(!callback(&f));CHECK(canonical_count==1+MODEL_JACOBIAN_COLOR_COUNT+NSTATES);
|
|
CHECK(f.run.jacobian_rhs-before==(unsigned long)canonical_count);
|
|
CHECK(f.run.jacobian_checks==1 && f.run.jacobian_mismatches==0);matrices_equal(&f);
|
|
f.context.colored=0;f.run.jacobian_colored=0;canonical_count=0;before=f.run.jacobian_rhs;
|
|
CHECK(!callback(&f));CHECK(canonical_count==1+NSTATES);
|
|
CHECK(f.run.jacobian_rhs-before==(unsigned long)canonical_count);matrices_equal(&f);
|
|
/* A rejected grouped probe retries columns without recomputing/mixing base. */
|
|
f.context.colored=1;f.run.jacobian_colored=1;reject_mode=1;canonical_count=0;before=f.run.jacobian_rhs;
|
|
CHECK(!callback(&f));CHECK(canonical_count==2+NSTATES);
|
|
CHECK(f.run.jacobian_rhs-before==(unsigned long)canonical_count);CHECK(f.run.jacobian_fallbacks==1);
|
|
reject_mode=0;matrices_equal(&f);
|
|
/* Base failure and cancellation return before any perturbation call. */
|
|
reject_canonical_base=1;canonical_count=0;before=f.run.jacobian_rhs;
|
|
CHECK(callback(&f)>0);CHECK(canonical_count==1 && f.run.jacobian_rhs-before==1);
|
|
CHECK(f.run.jacobian_fallbacks==1);reject_canonical_base=0;
|
|
canonical_count=0;before=f.run.jacobian_rhs;poll_calls=0;cancel_poll=1;
|
|
CHECK(callback(&f)<0);CHECK(canonical_count==0 && f.run.jacobian_rhs==before);
|
|
CHECK(f.run.jacobian_fallbacks==1);cancel_poll=0;
|
|
CHECK(f.run.nfev==f.run.jacobian_rhs);
|
|
CHECK(!memcmp(original_fy,N_VGetArrayPointer(f.fy),sizeof(original_fy)));
|
|
CHECK(!memcmp(state,N_VGetArrayPointer(f.y),sizeof(state)));
|
|
canonical_trace=0;fixture_free(&f);
|
|
}
|
|
#endif
|
|
int main(int argc,char **argv) {
|
|
if(argc!=2)return 64;
|
|
if(!strcmp(argv[1],"formula"))formula_oracle();
|
|
else if(!strcmp(argv[1],"structure"))coloring_validation();
|
|
else if(!strcmp(argv[1],"joint-failure"))recoverable_failure(1);
|
|
else if(!strcmp(argv[1],"individual-failure"))recoverable_failure(2);
|
|
else if(!strcmp(argv[1],"cancel"))cancellation();
|
|
else if(!strcmp(argv[1],"verify"))verify_missing_dependency();
|
|
else if(!strcmp(argv[1],"restart"))integration_restart_counters();
|
|
#if defined(MODEL_JACOBIAN_CANONICAL_RHS) && MODEL_JACOBIAN_CANONICAL_RHS
|
|
else if(!strcmp(argv[1],"canonical"))canonical_baseline();
|
|
#endif
|
|
else return 64;
|
|
printf("{\"case\":\"%s\",\"assertions\":%d,\"passed\":true}\n",argv[1],assertions);return 0;
|
|
}
|
|
'''
|
|
|
|
|
|
class NativeJacobianRuntimeTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if not sys.platform.startswith("linux"):
|
|
raise unittest.SkipTest("The independent private-ABI oracle currently uses Linux SUNDIALS 7.4 static libraries")
|
|
if not (SOURCE / "src/cvode/cvode_impl.h").is_file():
|
|
raise unittest.SkipTest("The local SUNDIALS 7.4 source tree is required for this private-ABI oracle")
|
|
compiler, sundials, _ = toolchain()
|
|
cls.directory = tempfile.TemporaryDirectory(prefix="native-jacobian-runtime-")
|
|
cls.addClassCleanup(cls.directory.cleanup)
|
|
directory = Path(cls.directory.name)
|
|
(directory / "model.h").write_text(MODEL_HEADER)
|
|
(directory / "harness.c").write_text(HARNESS.replace("@RUNTIME@", str(ROOT / "native/runtime/cvode_solver.c")))
|
|
cls.executable = directory / "harness"
|
|
libraries = [sundials / "lib" / f"libsundials_{name}.a" for name in LIBRARIES]
|
|
command = [compiler, "-std=c11", "-O3", "-Wall", "-Wextra", "-Werror", "-ffp-contract=off", "-fno-fast-math", "-D_POSIX_C_SOURCE=200809L"]
|
|
for include in (directory, ROOT / "native/include", sundials / "include", SOURCE / "src/cvode", SOURCE / "src/sundials"):
|
|
command += ["-I", str(include)]
|
|
command += [str(directory / "harness.c"), "-Wl,--start-group", *map(str, libraries), "-Wl,--end-group", "-lm", "-o", str(cls.executable)]
|
|
built = subprocess.run(command, capture_output=True, text=True, timeout=60)
|
|
if built.returncode:
|
|
raise AssertionError(built.stdout + built.stderr)
|
|
cls.canonical_executable = directory / "harness-canonical"
|
|
canonical_command = command[:-1] + [str(cls.canonical_executable), "-DMODEL_JACOBIAN_CANONICAL_RHS=1"]
|
|
built = subprocess.run(canonical_command, capture_output=True, text=True, timeout=60)
|
|
if built.returncode:
|
|
raise AssertionError(built.stdout + built.stderr)
|
|
|
|
def check_case(self, case, *, canonical=False):
|
|
executable = self.canonical_executable if canonical else self.executable
|
|
completed = subprocess.run([str(executable), case], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr)
|
|
self.assertIn('"passed":true', completed.stdout)
|
|
|
|
def test_actual_sundials_default_oracle_and_rounding(self):
|
|
self.check_case("formula")
|
|
|
|
def test_canonical_base_recomputed_original_fy_sets_increments_and_all_calls_count(self):
|
|
self.check_case("canonical", canonical=True)
|
|
|
|
def test_coloring_structure_and_bounds(self):
|
|
self.check_case("structure")
|
|
|
|
def test_joint_failure_falls_back_to_complete_dense(self):
|
|
self.check_case("joint-failure")
|
|
|
|
def test_individual_failure_remains_recoverable(self):
|
|
self.check_case("individual-failure")
|
|
|
|
def test_cancel_stops_without_fallback_or_phantom_rhs_count(self):
|
|
self.check_case("cancel")
|
|
|
|
def test_verify_detects_missing_dependency_and_stays_disabled_after_reinit(self):
|
|
self.check_case("verify")
|
|
|
|
def test_event_and_time_boundary_restart_counter_accounting(self):
|
|
self.check_case("restart")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|