forked from NIVANorge/Mobius
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmobius_model.h
1503 lines (1175 loc) · 48.9 KB
/
mobius_model.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#if !defined(MOBIUS_MODEL_H)
//NOTE: The purpose of having unit_h, input_h, equation_h etc. be structs that contain a numeric handle rather than just letting them be a handle directly is that we can then use the C++ type system to get type safety. Unfortunately, C++ does not allow you to typedef a unique copy of a type that is not interchangable with others. However the type system WILL distinguish between two differently named structs even though they are otherwise equal.
typedef u32 entity_handle;
#define MODEL_ENTITY_HANDLE(Type) struct Type \
{ \
entity_handle Handle; \
}; \
bool operator==(const Type &A, const Type &B) { return A.Handle == B.Handle; } \
bool operator!=(const Type &A, const Type &B) { return A.Handle != B.Handle; } \
bool operator<(const Type &A, const Type &B) { return A.Handle < B.Handle; } \
inline bool IsValid(Type H) { return H.Handle > 0; }
MODEL_ENTITY_HANDLE(unit_h)
MODEL_ENTITY_HANDLE(input_h)
MODEL_ENTITY_HANDLE(equation_h)
MODEL_ENTITY_HANDLE(parameter_double_h)
MODEL_ENTITY_HANDLE(parameter_uint_h)
MODEL_ENTITY_HANDLE(parameter_bool_h)
MODEL_ENTITY_HANDLE(parameter_time_h)
MODEL_ENTITY_HANDLE(solver_h)
MODEL_ENTITY_HANDLE(index_set_h)
MODEL_ENTITY_HANDLE(parameter_group_h)
#undef MODEL_ENTITY_HANDLE
enum entity_type //NOTE: Is currently only used so that the storage_structure knows what it is storing and can ask the Model for the name associated to a handle if an error occurs.
{
EntityType_Parameter,
EntityType_Input,
EntityType_Equation,
};
inline const char *
GetEntityTypeName(entity_type Type)
{
//NOTE: It is important that this matches the above enum:
const char *Typenames[3] = {"parameter", "input", "equation"};
return Typenames[(size_t)Type];
}
struct index_t
{
entity_handle IndexSetHandle;
u32 Index;
index_t() {};
index_t(index_set_h IndexSet, u32 Index) : IndexSetHandle(IndexSet.Handle), Index(Index) {};
index_t(entity_handle IndexSetHandle, u32 Index) : IndexSetHandle(IndexSetHandle), Index(Index) {};
void operator++()
{
Index++;
}
index_t operator+(s32 Add) const
{
index_t Result = *this;
Result.Index += Add; //NOTE: This could result in an underflow. Have to see if that is a problem
return Result;
}
index_t operator-(s32 Subtract) const
{
index_t Result = *this;
Result.Index -= Subtract; //NOTE: This could result in an underflow. Have to see if that is a problem
return Result;
}
bool operator<=(const index_t& Other) const
{
//NOTE: This does NOT check if they came from a different index set. That check has to be done by the caller if needed.
return Index <= Other.Index;
}
bool operator<(const index_t& Other) const
{
//NOTE: This does NOT check if they came from a different index set. That check has to be done by the caller if needed.
return Index < Other.Index;
}
operator size_t() const
{
return (size_t)Index;
}
};
union parameter_value
{
double ValDouble;
u64 ValUInt;
u64 ValBool; //NOTE: Since this is a union we don't save space by making the bool smaller any way.
datetime ValTime; //NOTE: From datetime.h
parameter_value() : ValTime() {}; //NOTE: 0-initializes it.
};
enum parameter_type
{
ParameterType_Double = 0,
ParameterType_UInt,
ParameterType_Bool,
ParameterType_Time,
};
inline const char *
GetParameterTypeName(parameter_type Type)
{
//NOTE: It is important that this matches the above enum:
const char *Typenames[4] = {"double", "uint", "bool", "time"};
return Typenames[(size_t)Type];
}
struct parameter_spec
{
const char *Name;
parameter_type Type;
parameter_value Min;
parameter_value Max;
parameter_value Default;
unit_h Unit;
const char *Description;
equation_h IsComputedBy; //NOTE: We allow certain parameters to be computed by an initial value equation rather than being provided by a parameter file.
bool ShouldNotBeExposed; //NOTE: Any user interface or file handler should not deal with a parameter if ShouldNotBeExposed = true;
parameter_group_h Group;
//NOTE: This not set before EndModelDefinition:
std::vector<index_set_h> IndexSetDependencies;
};
struct unit_spec
{
const char *Name;
//NOTE: We don't need to put anything else here at the moment. Maybe eventually?
};
struct value_set_accessor;
typedef std::function<double(value_set_accessor *)> mobius_equation;
typedef std::function<void(double *, double *)> mobius_solver_equation_function;
typedef std::function<void(size_t, size_t, double)> mobius_matrix_insertion_function;
typedef std::function<void(double *, mobius_matrix_insertion_function &)> mobius_solver_jacobi_function;
#define MOBIUS_SOLVER_FUNCTION(Name) void Name(double h, size_t n, double* x0, double* wk, const mobius_solver_equation_function &EquationFunction, const mobius_solver_jacobi_function &JacobiFunction, double AbsErr, double RelErr)
typedef MOBIUS_SOLVER_FUNCTION(mobius_solver_function);
struct parameter_group_spec
{
const char *Name;
parameter_group_h ParentGroup;
std::vector<parameter_group_h> ChildrenGroups;
index_set_h IndexSet;
std::vector<entity_handle> Parameters;
};
enum index_set_type
{
IndexSetType_Basic,
IndexSetType_Branched,
};
struct index_set_spec
{
const char *Name;
index_set_type Type;
std::vector<const char *> RequiredIndexes;
};
enum equation_type
{
EquationType_Basic,
EquationType_ODE,
EquationType_InitialValue,
EquationType_Cumulative,
};
inline const char *
GetEquationTypeName(equation_type Type)
{
//NOTE: It is important that this matches the above enum:
const char *Typenames[4] = {"basic", "ode", "initialvalue", "cumulative"};
return Typenames[(size_t)Type];
}
struct dependency_registration
{
entity_handle Handle;
size_t NumExplicitIndexes;
};
struct result_dependency_registration
{
entity_handle Handle;
std::vector<index_t> Indexes;
};
//TODO: See if we could unionize some of the data below. Not everything is needed by every type of equation.
struct equation_spec
{
const char *Name;
equation_type Type;
unit_h Unit;
parameter_double_h InitialValue;
double ExplicitInitialValue;
bool HasExplicitInitialValue;
equation_h InitialValueEquation;
bool ResetEveryTimestep; //NOTE: Only used for Type == EquationType_ODE.
bool EquationIsSet; //NOTE: Whether or not the equation body has been provided.
index_set_h CumulatesOverIndexSet; //NOTE: Only used for Type == EquationType_Cumulative.
equation_h Cumulates; //NOTE: Only used for Type == EquationType_Cumulative.
parameter_double_h CumulationWeight; //NOTE: Only used for Type == EquationType_Cumulative.
solver_h Solver;
//NOTE: The below are built during EndModelDefinition:
std::set<index_set_h> IndexSetDependencies; //NOTE: If the equation is run on a solver, the final index set dependencies of the equation will be those of the solver, not the ones stored here. You should generally use the storage structure to determine the final dependencies rather than this vector unless you are doing something specific in EndModelDefinition.
std::set<entity_handle> ParameterDependencies;
std::set<input_h> InputDependencies;
std::set<equation_h> DirectResultDependencies;
std::set<equation_h> DirectLastResultDependencies;
std::set<equation_h> CrossIndexResultDependencies;
std::vector<result_dependency_registration> IndexedResultAndLastResultDependencies; //TODO: Maybe don't store these here, we could keep them separately just locally in EndModelDefinition.
bool TempVisited; //NOTE: For use in a graph traversal algorithm while resolving dependencies.
bool Visited; //NOTE: For use in a graph traversal algorithm while resolving dependencies
};
struct solver_spec
{
const char *Name;
double h;
double RelErr;
double AbsErr;
mobius_solver_function *SolverFunction;
bool UsesErrorControl;
bool UsesJacobian;
//NOTE: These are built during EndModelDefinition:
std::set<index_set_h> IndexSetDependencies;
std::vector<equation_h> EquationsToSolve;
std::set<equation_h> DirectResultDependencies;
std::set<equation_h> CrossIndexResultDependencies;
bool TempVisited; //NOTE: For use in a graph traversal algorithm while resolving dependencies.
bool Visited; //NOTE: For use in a graph traversal algorithm while resolving dependencies
};
struct input_spec
{
const char *Name;
unit_h Unit;
bool IsAdditional;
std::vector<index_set_h> IndexSetDependencies;
};
//TODO: Find a better name for this struct?
struct iteration_data
{
std::vector<entity_handle> ParametersToRead;
std::vector<input_h> InputsToRead;
std::vector<equation_h> ResultsToRead;
std::vector<equation_h> LastResultsToRead;
};
enum equation_batch_type
{
BatchType_Regular,
BatchType_Solver,
};
struct equation_batch
{
equation_batch_type Type;
std::vector<equation_h> Equations;
solver_h Solver; //NOTE: Only for Type==BatchType_Solver.
std::vector<equation_h> EquationsODE; //NOTE: Only for Type==BatchType_Solver.
std::vector<equation_h> InitialValueOrder; //NOTE: The initial value setup of equations happens in a different order than the execution order during model run because the intial value equations may have different dependencies than the equations they are initial values for.
//NOTE: These are used for optimizing estimation of the Jacobian in case that is needed by a solver.
std::vector<std::vector<size_t>> ODEIsDependencyOfODE;
std::vector<std::vector<equation_h>> ODEIsDependencyOfNonODE;
};
struct equation_batch_group
{
std::vector<index_set_h> IndexSets;
std::vector<equation_h> LastResultsToReadAtBase; //Unfortunately we need this for LAST_RESULTs of equations with 0 index set dependencies.
std::vector<iteration_data> IterationData;
size_t FirstBatch;
size_t LastBatch;
};
struct storage_unit_specifier
{
std::vector<index_set_h> IndexSets;
std::vector<entity_handle> Handles;
};
struct mobius_model;
struct storage_structure
{
std::vector<storage_unit_specifier> Units;
size_t *TotalCountForUnit;
size_t *OffsetForUnit;
size_t *UnitForHandle;
size_t *LocationOfHandleInUnit; // Units[UnitForHandle[H]].Handles[LocationOfHandleInUnit[H]] == H;
size_t TotalCount;
bool HasBeenSetUp = false;
//NOTE: The following two are only here in case we need to look up the name of an index set or handle when reporting an error about misindexing if the MOBIUS_INDEX_BOUNDS_TESTS is turned on. It is not that clean to have this information here, though :(
const mobius_model *Model;
entity_type Type;
~storage_structure();
};
typedef std::unordered_map<token_string, entity_handle, token_string_hash_function> string_map;
struct mobius_data_set;
typedef std::function<void(mobius_data_set *)> mobius_preprocessing_step;
struct mobius_model
{
const char *Name;
const char *Version;
entity_handle FirstUnusedEquationHandle;
string_map EquationNameToHandle;
std::vector<mobius_equation> Equations;
std::vector<equation_spec> EquationSpecs;
entity_handle FirstUnusedInputHandle;
string_map InputNameToHandle;
std::vector<input_spec> InputSpecs;
entity_handle FirstUnusedParameterHandle;
string_map ParameterNameToHandle;
std::vector<parameter_spec> ParameterSpecs;
entity_handle FirstUnusedIndexSetHandle;
string_map IndexSetNameToHandle;
std::vector<index_set_spec> IndexSetSpecs;
entity_handle FirstUnusedParameterGroupHandle;
string_map ParameterGroupNameToHandle;
std::vector<parameter_group_spec> ParameterGroupSpecs;
entity_handle FirstUnusedSolverHandle;
string_map SolverNameToHandle;
std::vector<solver_spec> SolverSpecs;
entity_handle FirstUnusedUnitHandle;
string_map UnitNameToHandle;
std::vector<unit_spec> UnitSpecs;
std::vector<equation_batch> EquationBatches;
std::vector<equation_batch_group> BatchGroups;
std::vector<mobius_preprocessing_step> PreprocessingSteps;
timer DefinitionTimer;
bool Finalized;
};
#define FOR_ALL_BATCH_EQUATIONS(Batch, Do) \
for(equation_h Equation : Batch.Equations) { Do } \
if(Batch.Type == BatchType_Solver) { for(equation_h Equation : Batch.EquationsODE) { Do } }
#if !defined(MOBIUS_EQUATION_PROFILING)
#define MOBIUS_EQUATION_PROFILING 0
#endif
//TODO: The name "inputs" here is confusing, since there is already a different concept called input.
//TODO: Couldn't this just be a std::vector?
struct branch_inputs
{
size_t Count;
index_t *Inputs;
};
struct mobius_data_set
{
const mobius_model *Model;
parameter_value *ParameterData;
storage_structure ParameterStorageStructure;
double *InputData;
bool *InputTimeseriesWasProvided;
storage_structure InputStorageStructure;
datetime InputDataStartDate;
bool InputDataHasSeparateStartDate = false; //NOTE: Whether or not a start date was provided for the input data, which is potentially different from the start date of the model run.
u64 InputDataTimesteps;
double *ResultData;
storage_structure ResultStorageStructure;
index_t *IndexCounts;
const char ***IndexNames; // IndexNames[IndexSet.Handle][IndexNamesToHandle[IndexSet.Handle][IndexName]] == IndexName;
std::vector<string_map> IndexNamesToHandle;
bool AllIndexesHaveBeenSet;
branch_inputs **BranchInputs; //BranchInputs[ReachIndexSet][ReachIndex] ...
std::vector<parameter_value> FastParameterLookup;
std::vector<size_t> FastInputLookup;
std::vector<size_t> FastResultLookup;
std::vector<size_t> FastLastResultLookup;
double *x0; //NOTE: Temporary storage for use by solvers
double *wk; //NOTE: Temporary storage for use by solvers
bool HasBeenRun;
u64 TimestepsLastRun;
datetime StartDateLastRun;
~mobius_data_set();
};
struct value_set_accessor
{
// The purpose of the value set accessor is to store state during the run of the model as well as providing access to various values to each equation that gets evaluated.
// There are two use cases.
// If Running=false, this is a setup run, where the purpose is to register the accesses of all the equations to later determine their dependencies.
// If Running=true, this is the actual run of the model, where equations should have access to the actual parameter values and so on.
bool Running;
const mobius_model *Model;
mobius_data_set *DataSet;
s32 DayOfYear;
s32 DaysThisYear;
s64 Timestep; //NOTE: We make this a signed integer so that it can be set to -1 during the "initial value" step.
//NOTE: For use during model execution
parameter_value *CurParameters;
double *CurResults;
double *LastResults;
double *CurInputs;
bool *CurInputWasProvided;
index_t *CurrentIndexes; //NOTE: Contains the current index of each index set during execution.
double *AllCurResultsBase;
double *AllLastResultsBase;
double *AllCurInputsBase;
double *AtResult;
double *AtLastResult;
parameter_value *AtParameterLookup;
size_t *AtInputLookup;
size_t *AtResultLookup;
size_t *AtLastResultLookup;
//NOTE: For use during dependency registration:
std::vector<dependency_registration> ParameterDependencies;
std::vector<dependency_registration> InputDependencies;
std::vector<result_dependency_registration> ResultDependencies;
std::vector<result_dependency_registration> LastResultDependencies;
std::vector<index_set_h> DirectIndexSetDependencies;
#if MOBIUS_EQUATION_PROFILING
size_t *EquationHits;
u64 *EquationTotalCycles;
#endif
//NOTE: For dependency registration run:
value_set_accessor(const mobius_model *Model)
{
Running = false;
DataSet = 0;
this->Model = Model;
}
//NOTE: For proper run:
value_set_accessor(mobius_data_set *DataSet)
{
Running = true;
this->DataSet = DataSet;
this->Model = DataSet->Model;
CurInputs = AllocClearedArray(double, Model->FirstUnusedInputHandle);
CurParameters = AllocClearedArray(parameter_value, Model->FirstUnusedParameterHandle);
CurResults = AllocClearedArray(double, Model->FirstUnusedEquationHandle);
LastResults = AllocClearedArray(double, Model->FirstUnusedEquationHandle);
CurInputWasProvided = AllocClearedArray(bool, Model->FirstUnusedInputHandle);
CurrentIndexes = AllocClearedArray(index_t, Model->FirstUnusedIndexSetHandle);
for(entity_handle IndexSetHandle = 1; IndexSetHandle < Model->FirstUnusedIndexSetHandle; ++IndexSetHandle)
{
CurrentIndexes[IndexSetHandle].IndexSetHandle = IndexSetHandle;
}
DayOfYear = 0;
DaysThisYear = 365;
Timestep = 0;
}
~value_set_accessor()
{
if(Running)
{
free(CurParameters);
free(CurInputs);
free(CurInputWasProvided);
free(CurResults);
free(LastResults);
free(CurrentIndexes);
}
}
void Clear()
{
if(Running)
{
memset(CurParameters, 0, sizeof(parameter_value)*Model->FirstUnusedParameterHandle);
memset(CurInputs, 0, sizeof(double)*Model->FirstUnusedInputHandle);
memset(CurInputWasProvided, 0, sizeof(bool)*Model->FirstUnusedInputHandle);
memset(CurResults, 0, sizeof(double)*Model->FirstUnusedEquationHandle);
memset(LastResults, 0, sizeof(double)*Model->FirstUnusedEquationHandle);
memset(CurrentIndexes, 0, sizeof(index_t)*Model->FirstUnusedIndexSetHandle);
for(entity_handle IndexSetHandle = 1; IndexSetHandle < Model->FirstUnusedIndexSetHandle; ++IndexSetHandle)
{
CurrentIndexes[IndexSetHandle].IndexSetHandle = IndexSetHandle;
}
}
else
{
ParameterDependencies.clear();
InputDependencies.clear();
ResultDependencies.clear();
LastResultDependencies.clear();
DirectIndexSetDependencies.clear();
}
}
};
inline double
CallEquation(const mobius_model *Model, value_set_accessor *ValueSet, equation_h Equation)
{
#if MOBIUS_EQUATION_PROFILING
u64 Begin = __rdtsc();
#endif
double ResultValue = Model->Equations[Equation.Handle](ValueSet);
#if MOBIUS_EQUATION_PROFILING
u64 End = __rdtsc();
ValueSet->EquationHits[Equation.Handle]++;
ValueSet->EquationTotalCycles[Equation.Handle] += (End - Begin);
#endif
return ResultValue;
}
#define GET_ENTITY_NAME(Type, NType) \
inline const char * GetName(const mobius_model *Model, Type H) \
{ \
return Model->NType##Specs[H.Handle].Name; \
}
GET_ENTITY_NAME(equation_h, Equation)
GET_ENTITY_NAME(input_h, Input)
GET_ENTITY_NAME(parameter_double_h, Parameter)
GET_ENTITY_NAME(parameter_uint_h, Parameter)
GET_ENTITY_NAME(parameter_bool_h, Parameter)
GET_ENTITY_NAME(parameter_time_h, Parameter)
GET_ENTITY_NAME(index_set_h, IndexSet)
GET_ENTITY_NAME(parameter_group_h, ParameterGroup)
GET_ENTITY_NAME(solver_h, Solver)
GET_ENTITY_NAME(unit_h, Unit)
#undef GET_ENTITY_NAME
inline const char *
GetParameterName(const mobius_model *Model, entity_handle ParameterHandle) //NOTE: In case we don't know the type of the parameter and just want the name.
{
return Model->ParameterSpecs[ParameterHandle].Name;
}
inline const char *
GetName(const mobius_model *Model, entity_type Type, entity_handle Handle)
{
switch(Type)
{
case EntityType_Parameter:
return GetParameterName(Model, Handle);
break;
case EntityType_Input:
return GetName(Model, input_h {Handle});
break;
case EntityType_Equation:
return GetName(Model, equation_h {Handle});
break;
default:
MOBIUS_FATAL_ERROR("ERROR: (internal) ended up with wrong entity type!?" << std::endl);
return "unknown entity type";
}
}
#define GET_ENTITY_HANDLE(Type, Typename, Typename2) \
inline Type Get##Typename2##Handle(const mobius_model *Model, const token_string &Name) \
{ \
entity_handle Handle = 0; \
auto Find = Model->Typename##NameToHandle.find(Name); \
if(Find != Model->Typename##NameToHandle.end()) \
{ \
Handle = Find->second; \
} \
else \
{ \
MOBIUS_FATAL_ERROR("ERROR: Tried to look up the handle of the " << #Typename << " \"" << Name << "\", but it was not registered with the model." << std::endl); \
} \
return { Handle }; \
}
GET_ENTITY_HANDLE(equation_h, Equation, Equation)
GET_ENTITY_HANDLE(input_h, Input, Input)
GET_ENTITY_HANDLE(parameter_double_h, Parameter, ParameterDouble)
GET_ENTITY_HANDLE(parameter_uint_h, Parameter, ParameterUInt)
GET_ENTITY_HANDLE(parameter_bool_h, Parameter, ParameterBool)
GET_ENTITY_HANDLE(parameter_time_h, Parameter, ParameterTime)
GET_ENTITY_HANDLE(index_set_h, IndexSet, IndexSet)
GET_ENTITY_HANDLE(parameter_group_h, ParameterGroup, ParameterGroup)
GET_ENTITY_HANDLE(solver_h, Solver, Solver)
#undef GET_ENTITY_HANDLE
inline entity_handle
GetParameterHandle(const mobius_model *Model, const token_string &Name) //NOTE: In case we don't know the type of the parameter and just want the handle.
{
entity_handle Handle = 0;
auto Find = Model->ParameterNameToHandle.find(Name);
if(Find != Model->ParameterNameToHandle.end())
{
Handle = Find->second;
}
else
{
MOBIUS_FATAL_ERROR("ERROR: Tried to find the Parameter \"" << Name << "\", but it was not registered with the model." << std::endl);
}
return Handle;
}
#define REGISTRATION_BLOCK(Model) \
if(Model->Finalized) \
{ \
MOBIUS_FATAL_ERROR("ERROR: You can not call the function " << __func__ << " on the model after it has been finalized using EndModelDefinition." << std::endl); \
}
#define REGISTER_MODEL_ENTITY(Model, Typename, Handlename, Name) \
auto Find = Model->Typename##NameToHandle.find(Name); \
if(Find != Model->Typename##NameToHandle.end()) \
{ \
Handlename.Handle = Find->second; \
} \
else \
{ \
Handlename.Handle = Model->FirstUnused##Typename##Handle++; \
Model->Typename##NameToHandle[Name] = Handlename.Handle; \
} \
if(Model->Typename##Specs.size() <= Handlename.Handle) \
{ \
Model->Typename##Specs.resize(Handlename.Handle + 1, {}); \
} \
Model->Typename##Specs[Handlename.Handle].Name = Name;
void AddPreprocessingStep(mobius_model *Model, mobius_preprocessing_step PreprocessingStep)
{
REGISTRATION_BLOCK(Model);
Model->PreprocessingSteps.push_back(PreprocessingStep);
}
inline unit_h
RegisterUnit(mobius_model *Model, const char *Name = "dimensionless")
{
REGISTRATION_BLOCK(Model)
unit_h Unit = {};
REGISTER_MODEL_ENTITY(Model, Unit, Unit, Name);
return Unit;
}
inline index_set_h
RegisterIndexSet(mobius_model *Model, const char *Name, index_set_type Type = IndexSetType_Basic)
{
REGISTRATION_BLOCK(Model)
index_set_h IndexSet = {};
REGISTER_MODEL_ENTITY(Model, IndexSet, IndexSet, Name);
Model->IndexSetSpecs[IndexSet.Handle].Type = Type;
return IndexSet;
}
inline index_set_h
RegisterIndexSetBranched(mobius_model *Model, const char *Name)
{
REGISTRATION_BLOCK(Model)
index_set_h IndexSet = RegisterIndexSet(Model, Name, IndexSetType_Branched);
return IndexSet;
}
inline index_t
RequireIndex(mobius_model *Model, index_set_h IndexSet, const char *IndexName)
{
REGISTRATION_BLOCK(Model)
index_set_spec &Spec = Model->IndexSetSpecs[IndexSet.Handle];
if(Spec.Type != IndexSetType_Basic)
{
//TODO: Get rid of this requirement? However that may lead to issues with index order in branched index sets later.
MOBIUS_FATAL_ERROR("ERROR: We only allow requiring indexes for basic index sets, " << Spec.Name << " is of a different type." << std::endl);
}
auto Find = std::find(Spec.RequiredIndexes.begin(), Spec.RequiredIndexes.end(), IndexName);
if(Find != Spec.RequiredIndexes.end())
{
return index_t(IndexSet, (u32)std::distance(Spec.RequiredIndexes.begin(), Find)); //NOTE: This is its position in the vector.
}
else
{
Spec.RequiredIndexes.push_back(IndexName);
return index_t(IndexSet, (u32)(Spec.RequiredIndexes.size() - 1));
}
}
inline parameter_group_h
RegisterParameterGroup(mobius_model *Model, const char *Name, index_set_h IndexSet = {0})
{
REGISTRATION_BLOCK(Model)
parameter_group_h ParameterGroup = {};
REGISTER_MODEL_ENTITY(Model, ParameterGroup, ParameterGroup, Name)
Model->ParameterGroupSpecs[ParameterGroup.Handle].IndexSet = IndexSet;
return ParameterGroup;
}
inline void
SetParentGroup(mobius_model *Model, parameter_group_h Child, parameter_group_h Parent)
{
REGISTRATION_BLOCK(Model)
parameter_group_spec &ChildSpec = Model->ParameterGroupSpecs[Child.Handle];
parameter_group_spec &ParentSpec = Model->ParameterGroupSpecs[Parent.Handle];
if(IsValid(ChildSpec.ParentGroup) && ChildSpec.ParentGroup.Handle != Parent.Handle)
{
MOBIUS_FATAL_ERROR("ERROR: Setting a parent group for the parameter group " << ChildSpec.Name << ", but it already has a different parent group " << GetName(Model, ChildSpec.ParentGroup) << ".");
}
ChildSpec.ParentGroup = Parent;
ParentSpec.ChildrenGroups.push_back(Child);
}
inline input_h
RegisterInput(mobius_model *Model, const char *Name, unit_h Unit = {0}, bool IsAdditional = false)
{
REGISTRATION_BLOCK(Model)
input_h Input = {};
REGISTER_MODEL_ENTITY(Model, Input, Input, Name)
input_spec &Spec = Model->InputSpecs[Input.Handle];
Spec.IsAdditional = IsAdditional;
Spec.Unit = Unit;
return Input;
}
inline parameter_double_h
RegisterParameterDouble(mobius_model *Model, parameter_group_h Group, const char *Name, unit_h Unit, double Default, double Min = -DBL_MAX, double Max = DBL_MAX, const char *Description = 0)
{
REGISTRATION_BLOCK(Model)
parameter_double_h Parameter = {};
REGISTER_MODEL_ENTITY(Model, Parameter, Parameter, Name)
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
Spec.Type = ParameterType_Double;
Spec.Default.ValDouble = Default;
Spec.Min.ValDouble = Min;
Spec.Max.ValDouble = Max;
Spec.Group = Group;
Spec.Unit = Unit;
Spec.Description = Description;
Model->ParameterGroupSpecs[Group.Handle].Parameters.push_back(Parameter.Handle);
return Parameter;
}
inline parameter_uint_h
RegisterParameterUInt(mobius_model *Model, parameter_group_h Group, const char *Name, unit_h Unit, u64 Default, u64 Min = 0, u64 Max = 0xffffffffffffffff, const char *Description = 0)
{
REGISTRATION_BLOCK(Model)
parameter_uint_h Parameter = {};
REGISTER_MODEL_ENTITY(Model, Parameter, Parameter, Name)
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
Spec.Type = ParameterType_UInt;
Spec.Default.ValUInt = Default;
Spec.Min.ValUInt = Min;
Spec.Max.ValUInt = Max;
Spec.Group = Group;
Spec.Unit = Unit;
Spec.Description = Description;
Model->ParameterGroupSpecs[Group.Handle].Parameters.push_back(Parameter.Handle);
return Parameter;
}
inline parameter_bool_h
RegisterParameterBool(mobius_model *Model, parameter_group_h Group, const char *Name, bool Default, const char *Description = 0)
{
REGISTRATION_BLOCK(Model)
parameter_bool_h Parameter = {};
REGISTER_MODEL_ENTITY(Model, Parameter, Parameter, Name)
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
Spec.Type = ParameterType_Bool;
Spec.Default.ValBool = Default;
Spec.Min.ValBool = false;
Spec.Max.ValBool = true;
Spec.Group = Group;
Spec.Description = Description;
Model->ParameterGroupSpecs[Group.Handle].Parameters.push_back(Parameter.Handle);
return Parameter;
}
inline parameter_time_h
RegisterParameterDate(mobius_model *Model, parameter_group_h Group, const char *Name, const char *Default, const char *Min = "1000-1-1", const char *Max = "3000-12-31", const char *Description = 0)
{
REGISTRATION_BLOCK(Model)
parameter_time_h Parameter = {};
REGISTER_MODEL_ENTITY(Model, Parameter, Parameter, Name)
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
Spec.Type = ParameterType_Time;
bool ParseSuccessAll = true;
bool ParseSuccess;
Spec.Default.ValTime = datetime(Default, &ParseSuccess);
ParseSuccessAll = ParseSuccessAll && ParseSuccess;
Spec.Min.ValTime = datetime(Min, &ParseSuccess);
ParseSuccessAll = ParseSuccessAll && ParseSuccess;
Spec.Max.ValTime = datetime(Max, &ParseSuccess);
ParseSuccessAll = ParseSuccessAll && ParseSuccess;
if(!ParseSuccessAll)
{
MOBIUS_FATAL_ERROR("ERROR: Unrecognized date format for default, min or max value when registering the parameter " << Name << std::endl);
}
Spec.Group = Group;
Spec.Description = Description;
Model->ParameterGroupSpecs[Group.Handle].Parameters.push_back(Parameter.Handle);
return Parameter;
}
inline void
ParameterIsComputedBy(mobius_model *Model, parameter_double_h Parameter, equation_h Equation, bool ShouldNotBeExposed = true)
{
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
equation_spec &EqSpec = Model->EquationSpecs[Equation.Handle];
if(EqSpec.Type != EquationType_InitialValue)
{
MOBIUS_FATAL_ERROR("ERROR: Tried to set the equation " << EqSpec.Name << " to compute the parameter " << Spec.Name << ", but " << EqSpec.Name << " is not an initial value equation." << std::endl);
}
if(EqSpec.Unit != Spec.Unit)
{
MOBIUS_FATAL_ERROR("ERROR: The equation " << EqSpec.Name << " has a different unit from the parameter " << Spec.Name << ", that it is trying to compute." << std::endl);
}
Spec.IsComputedBy = Equation;
Spec.ShouldNotBeExposed = ShouldNotBeExposed;
}
inline void
ParameterIsComputedBy(mobius_model *Model, parameter_uint_h Parameter, equation_h Equation, bool ShouldNotBeExposed = true)
{
parameter_spec &Spec = Model->ParameterSpecs[Parameter.Handle];
equation_spec &EqSpec = Model->EquationSpecs[Equation.Handle];
if(EqSpec.Type != EquationType_InitialValue)
{
MOBIUS_FATAL_ERROR("ERROR: Tried to set the equation " << EqSpec.Name << " to compute the parameter " << Spec.Name << ", but " << EqSpec.Name << " is not an initial value equation." << std::endl);
}
if(EqSpec.Unit != Spec.Unit)
{
MOBIUS_FATAL_ERROR("ERROR: The equation " << EqSpec.Name << " has a different unit from the parameter " << Spec.Name << ", that it is trying to compute." << std::endl);
}
Spec.IsComputedBy = Equation;
Spec.ShouldNotBeExposed = ShouldNotBeExposed;
}
inline void
SetEquation(mobius_model *Model, equation_h Equation, mobius_equation EquationBody, bool Override = false)
{
//REGISTRATION_BLOCK(Model) //NOTE: We can't use REGISTRATION_BLOCK since the user don't call the SetEquation explicitly, it is called through the macro EQUATION, and so they would not understand the error message.
if(Model->Finalized)
{
MOBIUS_FATAL_ERROR("ERROR: You can not define an EQUATION body for the model after it has been finalized using EndModelDefinition." << std::endl);
}
if(!Override && Model->EquationSpecs[Equation.Handle].EquationIsSet)
{
MOBIUS_FATAL_ERROR("ERROR: The equation body for " << GetName(Model, Equation) << " is already defined. It can not be defined twice unless it is explicitly overridden." << std::endl);
}
Model->Equations[Equation.Handle] = EquationBody;
Model->EquationSpecs[Equation.Handle].EquationIsSet = true;
}
static equation_h
RegisterEquation(mobius_model *Model, const char *Name, unit_h Unit, equation_type Type = EquationType_Basic)
{
REGISTRATION_BLOCK(Model)
equation_h Equation = {};
REGISTER_MODEL_ENTITY(Model, Equation, Equation, Name)
if(Model->Equations.size() <= Equation.Handle)
{
Model->Equations.resize(Equation.Handle + 1, {});
}
Model->EquationSpecs[Equation.Handle].Type = Type;
Model->EquationSpecs[Equation.Handle].Unit = Unit;
return Equation;
}
inline equation_h
RegisterEquationODE(mobius_model *Model, const char *Name, unit_h Unit)
{
REGISTRATION_BLOCK(Model)