@@ -373,6 +373,25 @@ function generateFmi3FunctionsC(
373373 L . push ( " fmi3Float64 stepSize;" ) ;
374374 L . push ( "} FMU3Instance;" ) ;
375375 L . push ( "" ) ;
376+ L . push ( "/* Variable size lookup table for array batching (FMI 3.0 §2.2.7) */" ) ;
377+ L . push ( "static const size_t varSizes[N_VARS + 1] = {" ) ;
378+ const sizeEntries : number [ ] = new Array ( nVars ) . fill ( 1 ) ;
379+ for ( const sv of result . variables ) {
380+ if ( sv . dimensions && sv . dimensions . length > 0 ) {
381+ let totalSize = 1 ;
382+ for ( const dim of sv . dimensions ) {
383+ if ( dim . start !== undefined ) totalSize *= dim . start ;
384+ }
385+ if ( sv . valueReference < nVars ) sizeEntries [ sv . valueReference ] = totalSize ;
386+ }
387+ }
388+ for ( let i = 0 ; i < sizeEntries . length ; i += 20 ) {
389+ const batch = sizeEntries . slice ( i , Math . min ( i + 20 , sizeEntries . length ) ) ;
390+ const isLast = i + 20 >= sizeEntries . length ;
391+ L . push ( ` ${ batch . join ( "," ) } ${ isLast ? "" : "," } ` ) ;
392+ }
393+ L . push ( "};" ) ;
394+ L . push ( "" ) ;
376395
377396 // fmi3InstantiateCoSimulation
378397 L . push ( "fmi3Instance fmi3InstantiateCoSimulation(" ) ;
@@ -431,53 +450,133 @@ function generateFmi3FunctionsC(
431450 L . push ( "fmi3Status fmi3Reset(fmi3Instance instance) { (void)instance; return fmi3OK; }" ) ;
432451 L . push ( "" ) ;
433452
434- // Get/Set Float64
453+ // Get/Set Float64 (array-aware batching)
435454 L . push (
436455 "fmi3Status fmi3GetFloat64(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Float64 value[], size_t nValues) {" ,
437456 ) ;
438- L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
439- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = inst->model.vars[vr[i]]; }" ) ;
457+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
458+ L . push ( " size_t vi = 0;" ) ;
459+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
460+ L . push ( " if (vr[i] < N_VARS) {" ) ;
461+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
462+ L . push ( " for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = inst->model.vars[vr[i] + j];" ) ;
463+ L . push ( " }" ) ;
464+ L . push ( " }" ) ;
440465 L . push ( " return fmi3OK;" ) ;
441466 L . push ( "}" ) ;
442467 L . push (
443468 "fmi3Status fmi3SetFloat64(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Float64 value[], size_t nValues) {" ,
444469 ) ;
445- L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
446- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = value[i]; }" ) ;
470+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
471+ L . push ( " size_t vi = 0;" ) ;
472+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
473+ L . push ( " if (vr[i] < N_VARS) {" ) ;
474+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
475+ L . push ( " for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = value[vi++];" ) ;
476+ L . push ( " }" ) ;
477+ L . push ( " }" ) ;
447478 L . push ( " return fmi3OK;" ) ;
448479 L . push ( "}" ) ;
449480 L . push ( "" ) ;
450481
451- // Get/Set Int32
482+ // Get/Set Int32 (array-aware batching)
452483 L . push (
453484 "fmi3Status fmi3GetInt32(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Int32 value[], size_t nValues) {" ,
454485 ) ;
455- L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
456- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = (fmi3Int32)inst->model.vars[vr[i]]; }" ) ;
486+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
487+ L . push ( " size_t vi = 0;" ) ;
488+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
489+ L . push ( " if (vr[i] < N_VARS) {" ) ;
490+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
491+ L . push ( " for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = (fmi3Int32)inst->model.vars[vr[i] + j];" ) ;
492+ L . push ( " }" ) ;
493+ L . push ( " }" ) ;
457494 L . push ( " return fmi3OK;" ) ;
458495 L . push ( "}" ) ;
459496 L . push (
460497 "fmi3Status fmi3SetInt32(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Int32 value[], size_t nValues) {" ,
461498 ) ;
462- L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
463- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = (double)value[i]; }" ) ;
499+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
500+ L . push ( " size_t vi = 0;" ) ;
501+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
502+ L . push ( " if (vr[i] < N_VARS) {" ) ;
503+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
504+ L . push ( " for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = (double)value[vi++];" ) ;
505+ L . push ( " }" ) ;
506+ L . push ( " }" ) ;
464507 L . push ( " return fmi3OK;" ) ;
465508 L . push ( "}" ) ;
466509 L . push ( "" ) ;
467510
468- // Get/Set Boolean
511+ // Get/Set Boolean (array-aware batching)
469512 L . push (
470513 "fmi3Status fmi3GetBoolean(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Boolean value[], size_t nValues) {" ,
471514 ) ;
472- L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
473- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = inst->model.vars[vr[i]] != 0.0; }" ) ;
515+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
516+ L . push ( " size_t vi = 0;" ) ;
517+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
518+ L . push ( " if (vr[i] < N_VARS) {" ) ;
519+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
520+ L . push ( " for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = inst->model.vars[vr[i] + j] != 0.0;" ) ;
521+ L . push ( " }" ) ;
522+ L . push ( " }" ) ;
474523 L . push ( " return fmi3OK;" ) ;
475524 L . push ( "}" ) ;
476525 L . push (
477526 "fmi3Status fmi3SetBoolean(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Boolean value[], size_t nValues) {" ,
478527 ) ;
528+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
529+ L . push ( " size_t vi = 0;" ) ;
530+ L . push ( " for (size_t i = 0; i < nvr && vi < nValues; i++) {" ) ;
531+ L . push ( " if (vr[i] < N_VARS) {" ) ;
532+ L . push ( " size_t sz = varSizes[vr[i]];" ) ;
533+ L . push (
534+ " for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = value[vi++] ? 1.0 : 0.0;" ,
535+ ) ;
536+ L . push ( " }" ) ;
537+ L . push ( " }" ) ;
538+ L . push ( " return fmi3OK;" ) ;
539+ L . push ( "}" ) ;
540+ L . push ( "" ) ;
541+
542+ // Get/Set String
543+ L . push (
544+ "fmi3Status fmi3GetString(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3String value[], size_t nValues) {" ,
545+ ) ;
479546 L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
480- L . push ( " for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = value[i] ? 1.0 : 0.0; }" ) ;
547+ L . push (
548+ ' for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_STRING_VARS) value[i] = inst->model.stringVars[vr[i]] ? inst->model.stringVars[vr[i]] : ""; }' ,
549+ ) ;
550+ L . push ( " return fmi3OK;" ) ;
551+ L . push ( "}" ) ;
552+ L . push (
553+ "fmi3Status fmi3SetString(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3String value[], size_t nValues) {" ,
554+ ) ;
555+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;" ) ;
556+ L . push ( " for (size_t i = 0; i < nvr; i++) {" ) ;
557+ L . push ( " if (vr[i] < N_STRING_VARS) {" ) ;
558+ L . push ( " if (inst->model.stringVars[vr[i]]) free(inst->model.stringVars[vr[i]]);" ) ;
559+ L . push ( " inst->model.stringVars[vr[i]] = value[i] ? strdup(value[i]) : NULL;" ) ;
560+ L . push ( " }" ) ;
561+ L . push ( " }" ) ;
562+ L . push ( " return fmi3OK;" ) ;
563+ L . push ( "}" ) ;
564+ L . push ( "" ) ;
565+
566+ // Get/Set Binary (FMI 3.0 opaque payload)
567+ L . push (
568+ "fmi3Status fmi3GetBinary(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, size_t sizes[], fmi3Binary value[], size_t nValues) {" ,
569+ ) ;
570+ L . push ( " (void)instance; (void)vr; (void)nvr; (void)sizes; (void)value; (void)nValues;" ) ;
571+ L . push ( " /* Binary variables are not produced by Modelica — stub returns empty */" ) ;
572+ L . push ( " for (size_t i = 0; i < nvr; i++) { sizes[i] = 0; value[i] = NULL; }" ) ;
573+ L . push ( " return fmi3OK;" ) ;
574+ L . push ( "}" ) ;
575+ L . push (
576+ "fmi3Status fmi3SetBinary(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const size_t sizes[], const fmi3Binary value[], size_t nValues) {" ,
577+ ) ;
578+ L . push ( " (void)instance; (void)vr; (void)nvr; (void)sizes; (void)value; (void)nValues;" ) ;
579+ L . push ( " /* Binary variables are not produced by Modelica — stub ignores */" ) ;
481580 L . push ( " return fmi3OK;" ) ;
482581 L . push ( "}" ) ;
483582 L . push ( "" ) ;
@@ -627,6 +726,65 @@ function generateFmi3FunctionsC(
627726 L . push (
628727 "fmi3Status fmi3SetDebugLogging(fmi3Instance instance, fmi3Boolean loggingOn, size_t nCategories, const fmi3String categories[]) { ((FMU3Instance*)instance)->loggingOn = loggingOn; (void)nCategories; (void)categories; return fmi3OK; }" ,
629728 ) ;
729+ L . push ( "" ) ;
730+
731+ // fmi3GetDirectionalDerivative — forward-mode Jacobian-vector product via finite differences
732+ L . push ( "/* Directional derivative: dz = (∂z/∂v) · dv (forward-mode via finite differences) */" ) ;
733+ L . push (
734+ "fmi3Status fmi3GetDirectionalDerivative(fmi3Instance instance, const fmi3ValueReference unknowns[], size_t nUnknowns, const fmi3ValueReference knowns[], size_t nKnowns, const fmi3Float64 seed[], size_t nSeed, fmi3Float64 sensitivity[], size_t nSensitivity) {" ,
735+ ) ;
736+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
737+ L . push ( " (void)nSeed; (void)nSensitivity;" ) ;
738+ L . push ( " const double h = 1e-8;" ) ;
739+ L . push ( " /* Save original known values */" ) ;
740+ L . push ( " double* saved = (double*)malloc(nKnowns * sizeof(double));" ) ;
741+ L . push ( " if (!saved) return fmi3Error;" ) ;
742+ L . push ( " for (size_t i = 0; i < nKnowns; i++) saved[i] = inst->model.vars[knowns[i]];" ) ;
743+ L . push ( " /* Evaluate f(v) */" ) ;
744+ L . push ( " double* f0 = (double*)malloc(nUnknowns * sizeof(double));" ) ;
745+ L . push ( " if (!f0) { free(saved); return fmi3Error; }" ) ;
746+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
747+ L . push ( " for (size_t i = 0; i < nUnknowns; i++) f0[i] = inst->model.vars[unknowns[i]];" ) ;
748+ L . push ( " /* Perturb: v += h * seed */" ) ;
749+ L . push ( " for (size_t i = 0; i < nKnowns; i++) inst->model.vars[knowns[i]] = saved[i] + h * seed[i];" ) ;
750+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
751+ L . push ( " /* sensitivity = (f(v + h*seed) - f(v)) / h */" ) ;
752+ L . push ( " for (size_t i = 0; i < nUnknowns; i++) sensitivity[i] = (inst->model.vars[unknowns[i]] - f0[i]) / h;" ) ;
753+ L . push ( " /* Restore */" ) ;
754+ L . push ( " for (size_t i = 0; i < nKnowns; i++) inst->model.vars[knowns[i]] = saved[i];" ) ;
755+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
756+ L . push ( " free(saved); free(f0);" ) ;
757+ L . push ( " return fmi3OK;" ) ;
758+ L . push ( "}" ) ;
759+ L . push ( "" ) ;
760+
761+ // fmi3GetAdjointDerivative — reverse-mode via column-wise finite differences
762+ L . push ( "/* Adjoint derivative: dv = (∂z/∂v)ᵀ · δz (reverse-mode via column-wise FD) */" ) ;
763+ L . push (
764+ "fmi3Status fmi3GetAdjointDerivative(fmi3Instance instance, const fmi3ValueReference unknowns[], size_t nUnknowns, const fmi3ValueReference knowns[], size_t nKnowns, const fmi3Float64 seed[], size_t nSeed, fmi3Float64 sensitivity[], size_t nSensitivity) {" ,
765+ ) ;
766+ L . push ( " FMU3Instance* inst = (FMU3Instance*)instance;" ) ;
767+ L . push ( " (void)nSeed; (void)nSensitivity;" ) ;
768+ L . push ( " const double h = 1e-8;" ) ;
769+ L . push ( " /* Evaluate f(v) */" ) ;
770+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
771+ L . push ( " double* f0 = (double*)malloc(nUnknowns * sizeof(double));" ) ;
772+ L . push ( " if (!f0) return fmi3Error;" ) ;
773+ L . push ( " for (size_t i = 0; i < nUnknowns; i++) f0[i] = inst->model.vars[unknowns[i]];" ) ;
774+ L . push ( " /* Column-wise FD: for each known, perturb and compute Jacobian column, then dot with seed */" ) ;
775+ L . push ( " for (size_t j = 0; j < nKnowns; j++) {" ) ;
776+ L . push ( " double orig = inst->model.vars[knowns[j]];" ) ;
777+ L . push ( " inst->model.vars[knowns[j]] = orig + h;" ) ;
778+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
779+ L . push ( " double dot = 0.0;" ) ;
780+ L . push ( " for (size_t i = 0; i < nUnknowns; i++) dot += ((inst->model.vars[unknowns[i]] - f0[i]) / h) * seed[i];" ) ;
781+ L . push ( " sensitivity[j] = dot;" ) ;
782+ L . push ( " inst->model.vars[knowns[j]] = orig;" ) ;
783+ L . push ( " }" ) ;
784+ L . push ( ` ${ id } _getDerivatives(&inst->model);` ) ;
785+ L . push ( " free(f0);" ) ;
786+ L . push ( " return fmi3OK;" ) ;
787+ L . push ( "}" ) ;
630788
631789 // FMU state management
632790 L . push (
0 commit comments