• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nasa / cml / 29260307857

13 Jul 2026 02:59PM UTC coverage: 78.251% (+1.3%) from 76.929%
29260307857

push

github

web-flow
Instrument developer builds with address and undefined behavior sanitizers (#34)

Additionally, fixed leaks and undefined behavior that we have control over. 
- The fault manager model and the XML Helper unit sim weren't properly freeing
  the xmlDocPtr returned from xmlParseFile.
- It was previously possible to get the Generic Multi-Input Table model to read
  from invalid memory if the user loaded new data into one of its constituent
  independent variables that was the wrong size.

Added suppression files for leaks and undefined behavior that we don't have
control over, like SWIG and udunits leaks.

Refs #33

7752 of 9390 branches covered (82.56%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 3 files covered. (100.0%)

98 existing lines in 38 files now uncovered.

17894 of 23384 relevant lines covered (76.52%)

100261.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.72
/models/utilities/table_interp_cpp/src/generic_multi_input_table.cc
1
/*******************************TRICK HEADER***********************************
2
PURPOSE:
3
  (definitions for the single-variable methods for the n-dimensional lookup tables)
4

5
PROGRAMMERS:
6
  (((Gary Turner) (OSR) (dec 2015) (Antares) (initial version))
7
   ((Bingquan Wang) (OSR) (aug 2017) (Antares) (IVV code cleanup and refactored))
8
  )
9
*******************************************************************************/
10

11
#include <cstddef> // NULL
12

13
#include "../include/generic_multi_input_table.hh"
14

15
// NOTE - using [index] rather than .at(index) to index STL-vectors primarily
16
//        because doing so is much faster.
17
//        The .at(index) notation is generally considered safer because it
18
//        includes checking for index being inside bounds; that checking is
19
//        managed in the code independently so that index is checked only
20
//        once per setting instead of at every usage, and the checking is
21
//        typically implicit to how the loop is configured rather than
22
//        explicit on the value itself.
23

24

25
/*****************************************************************************
26
Constructor
27
*****************************************************************************/
28
GenericMultiInputTable::GenericMultiInputTable()
3,192✔
29
  :
30
  trivial_case(true),
31
  output_ptrs_set(false),
32
  data_loaded(false),
33
  initialized(false),
34
  data(),
35
  size_of_dimension(),
36
  data_point_weight(),
37
  data_point_index(),
38
  num_data_elements_per_increment_of_index(),
39
  output(),
40
  independents()
3,192✔
41
{}
3,192✔
42
/****************************************************************************/
43
GenericMultiInputTable::GenericMultiInputTable(
58✔
44
          double *dependent_variables, // array of variables
45
          size_t num_vars)
58✔
46
  :
47
  GenericMultiInputTable()
59✔
48
{
49
  if (dependent_variables == nullptr) {
58✔
50
    CMLMessage::fail(__FILE__,__LINE__,"Construction error: ",
4✔
51
       "The input parameter \"dependent_variables\" cannot be NULL. \n");
52
  }
53
  for (unsigned int ii = 0; ii < num_vars; ++ii) {
281✔
54
    output.push_back(dependent_variables + ii);
224✔
55
  }
56
  output_ptrs_set = true;
57✔
57
}
57✔
58
/****************************************************************************/
59
GenericMultiInputTable::GenericMultiInputTable(
2,443✔
60
          double & dependent_var)
2,443✔
61
  :
62
  GenericMultiInputTable()
2,443✔
63
{
64
  output.push_back(&dependent_var);
2,443✔
65
  output_ptrs_set = true;
2,443✔
66
}
2,443✔
67
/****************************************************************************/
68
GenericMultiInputTable::GenericMultiInputTable(
192✔
69
          const DoublePtrVec & dependent_variables)
192✔
70
  :
71
  GenericMultiInputTable()
192✔
72
{
73
  populate_output(dependent_variables);
192✔
74
}
192✔
75

76
/*****************************************************************************
77
load_data
78
Purpose:(Loads the data into the class structure)
79
*****************************************************************************/
80
bool
81
GenericMultiInputTable::load_data(
338✔
82
            const double *data_in,
83
            const SizeVec &dim_list)
84
{
85
  // run common checks for validity
86
  if( !load_data_internal_check(dim_list)) {
338✔
87
    return false;
1✔
88
  }
89
  // Copy the input data to the class
90
  return copy_data(data_in);  //NULL check in copy_data()
337✔
91
}
92
/****************************************************************************/
93
bool
94
GenericMultiInputTable::load_data(
208✔
95
            const DoubleVec  & data_in,
96
            const SizeVec &dim_list)
97
{
98
  // run common checks for validity
99
  if( !load_data_internal_check(dim_list)) {
208✔
100
    return false;
3✔
101
  }
102
  // Copy the input data to the class
103
  return copy_data(data_in);
205✔
104
}
105

106
/*****************************************************************************
107
load_data_internal_check
108
Purpose:(Perform internal checks on data, common to both methods of loading
109
         data)
110
*****************************************************************************/
111
bool
112
GenericMultiInputTable::load_data_internal_check(
546✔
113
            const SizeVec &dim_list)
114
{
115
  if (dim_list.size() < 2) {
546✔
116
    CMLMessage::error(
2✔
117
      __FILE__,__LINE__,"Table data-load failure:\n",
118
      "There must be at least 2 dimensions to the data table so the\n"
119
      "2nd argument must be a vector of at least size 2.\n"
120
      "The first entry of this vector indicates how many dependent variables\n"
121
      "are populated from this table; each subsequent entry indicates how\n"
122
      "many calibration points there are for each of the independent "
123
      "variables.\nA vector without at least 2 entries is incomplete.\n"
124
      "Aborting load_data().\n");
125
    return false;
2✔
126
  }
127

128
  if (dim_list.at(0) != output.size()) {
544✔
129
    CMLMessage::error(
4✔
130
      __FILE__,__LINE__,"Table data-load failure:\n",
131
      "The specified number of dependent variables (",dim_list.at(0),
2✔
132
      ") does not match\n"
133
      "with the number of interpolation outputs (", output.size(), ").\n"
2✔
134
      "Aborting load_data().\n");
135
    return false;
2✔
136
  }
137

138
  if (data_loaded) {
542✔
139
    CMLMessage::warn(
2✔
140
      __FILE__,__LINE__,"Table data-load error\n",
141
      "Data has already been loaded for this table.\n"
142
      "Rewriting data.\n");
143
    data.clear();
2✔
144
  }
145

146
  // data file is loaded as a single-dimensioned array; this model keeps track
147
  // of the range of indices at which each index on each dimension accesses
148
  // that array.  The size_of_dimension vector records the number of elements
149
  // on each dimension.
150
  size_of_dimension = dim_list;
542✔
151
  return true;
542✔
152
}
153

154
/*****************************************************************************
155
add_dependent
156
Purpose:(Adds a dependent variable.)
157
*****************************************************************************/
158
void
159
GenericMultiInputTable::add_dependent(
299✔
160
    double & new_dep_var)
161
{
162
  if (initialized) {
299✔
163
    CMLMessage::error(
1✔
164
      __FILE__,__LINE__,"Table-variable connection error:\n",
165
      "Cannot add new dependent variables after initialization.\n"
166
      "Addition failed.\n");
167
      return;
1✔
168
  }
169
  output.push_back(&new_dep_var);
298✔
170
  output_ptrs_set = true;
298✔
171
}
172

173
/*****************************************************************************
174
add_dependent_data
175
Purpose:(Adds a dependent variable and its associated data.)
176
*****************************************************************************/
177
void
178
GenericMultiInputTable::append_dependent_data(
×
179
    double & new_dep_var,
180
    const DoubleVec & data_vec,
181
    const SizeVec &dim_list)
182
{
183
  if (initialized) {
×
184
    CMLMessage::error( __FILE__,__LINE__,
×
185
      "Cannot add new dependent variables after initialization.\n"
186
      "Addition failed.\n");
187
      return;
×
188
  }
189
  if (!data_loaded) {
×
190
    CMLMessage::error( __FILE__,__LINE__,
×
191
      "Cannot append data to a table until it has data already loaded.\n"
192
      "Addition failed.\n");
193
      return;
×
194
  }
195
  if ( dim_list.size() != size_of_dimension.size()-1) {
×
196
    CMLMessage::error( __FILE__,__LINE__,
×
197
      "Dimension of new data array does not match that of existing data"
198
      "sub-array.\n"
UNCOV
199
      "New data is specified to have ",dim_list.size()," dimensions:\n"
×
200
      "Existing data is specified to have ", size_of_dimension.size()-1,
×
201
      "dimensions for each dependent variable:\n"
202
      "Addition failed.\n");
203
      return;
×
204
  }
205
  for (size_t ii = 0; ii < dim_list.size(); ++ii) {
×
206
    if ( dim_list[ii] != size_of_dimension[ii+1] ) {
×
207
      CMLMessage::error( __FILE__,__LINE__,
×
208
        "Number of elements in dimension ",ii," of new data does not\n"
209
        "match with that of existing data.\n"
210
        "New data specifies      ",dim_list[ii]," elements.\n"
×
211
        "Existing data specifies ",size_of_dimension[ii+1]," elements.\n"
×
212
        "Addition failed.\n");
213
        return;
×
214
    }
215
  }
216
  // Checks passed
217
  output.push_back(&new_dep_var);
×
218
  output_ptrs_set = true;
×
219
  size_of_dimension[0]++;
×
220
  data.insert(data.end(), data_vec.begin(), data_vec.end());
×
221
}
222

223
/*****************************************************************************
224
add_independent
225
Purpose:(Adds an independent variable to either:
226
         - the back of the list of independents, or
227
         - to a specified location)
228
*****************************************************************************/
229
void
230
GenericMultiInputTable::add_independent(
416✔
231
        TableIndependentVariable             & var,
232
        TableIndependentVariable::LookupMethod lookup_method)
233
{
234
  // simply add to the end of the list.
235
  add_independent( var, independents.size(), lookup_method);
416✔
236
}
416✔
237
//*****************************************************************************
238
void
239
GenericMultiInputTable::add_independent(
599✔
240
        TableIndependentVariable & var,
241
        size_t index,
242
        TableIndependentVariable::LookupMethod lookup_method)
243
{
244
  // Only succeed if model has not been initialized already
245
  if (initialized) {
599✔
246
    CMLMessage::error(
1✔
247
      __FILE__,__LINE__,"Table-variable connection error:\n",
248
      "Cannot add new independent variables after initialization.\n"
249
      "Addition failed.\n");
250
    return;
1✔
251
  }
252

253
  // size() is used multiple times, so just store it:
254
  const size_t size = independents.size();
598✔
255

256
  // check input for duplication
257
  for (size_t ii = 0; ii < size; ++ii) {
959✔
258
    if (&var == independents[ii].first) {
362✔
259
      CMLMessage::error(
1✔
260
        __FILE__,__LINE__,"Redundant configuration\n",
261
        "Attempted to add an TableIndependentVariable instance (name: ",
262
        var.get_name_char(),")\n"
1✔
263
        "to a GenericMultiInputTable at index ", index, "\n"
264
        "but the table already has access to that TableIndependentVariable, "
265
        "at index ",ii,"\n"
266
        "Aborting redundant addition.\n"
267
        "TableIndependentVariable remains at index ",ii,"\n");
268
      return;
1✔
269
    }
270
  }
271

272
  if (index < size) {
597✔
273
    if (independents[index].first != NULL) {
34✔
274
      CMLMessage::warn(
2✔
275
      __FILE__,__LINE__,"Table-variable connection error:\n",
276
      "Connecting dimension ",index, " of the table with an independent "
277
      "variable (", var.get_name_char(),").\n"
1✔
278
      "This dimension was previously populated with a different independent\n"
279
      "variable (",independents[index].first->get_name_char(),"\n");
1✔
280
    }
281
    independents[index] = IndepPair(&var, lookup_method);
34✔
282
  }
283
  else {
284
    // Fill the independents with NULL values up to the specified index.
285
    TableIndependentVariable * null_ptr = NULL;
563✔
286
    for (size_t ii = size; ii < index; ++ii) {
596✔
287
      independents.push_back(IndepPair(null_ptr, lookup_method));
33✔
288
    }
289
    // Then add the specified independent at the back.
290
    independents.push_back(IndepPair(&var, lookup_method));
563✔
291
  }
292
}
293

294
/*****************************************************************************
295
initialize
296
Purpose:(Checks for data availability and consistency)
297
*****************************************************************************/
298
bool
299
GenericMultiInputTable::initialize()
379✔
300
{
301
  if (initialized) {
379✔
302
    CMLMessage::warn(
1✔
303
      __FILE__,__LINE__,"Redundant initialization:\n",
304
      "Called the initialize method redundantly.\n"
305
      "The initialize method has already completed successfully.\n");
306
    return true;
1✔
307
  }
308

309
  if (!data_loaded) {
378✔
310
    CMLMessage::error(
2✔
311
      __FILE__,__LINE__,"Initialization error:\n",
312
      "Called the initialize method before the data table\n"
313
      "has been populated with data.\n"
314
      "Must call load_data first.\n"
315
      "Initialization failed.\n");
316
    return false;
2✔
317
  }
318

319
  if (!output_ptrs_set) {
376✔
320
    CMLMessage::error( __FILE__,__LINE__,
×
321
      "initialization failed:\n",
322
      "The output pointers have not been set, data has nowhere to go.\n");
323
    return false;
×
324
  }
325

326
  // number of dimensions of data in the array:
327
  size_t data_dimension = size_of_dimension.size();
376✔
328
  // number of independent variables:
329
  size_t num_independents = independents.size();
376✔
330

331
  // NOTE:
332
  // The first "dimension" of the table is always reserved to provide 1
333
  // "level" per dependent variable.  So size_of_dimension[0] provides
334
  // the number of dependent variables populated by this table. This is
335
  // true even in the simplest case with only 1 dependent variable. The
336
  // second "dimension" contains data associated with the first independent
337
  // variable, with each additional independent variable adding another
338
  // "dimension".
339
  // E.g. think of this as an array:
340
  //   data[3][7][1]
341
  // addresses a value of the 4th (index-3) dependent variable when the
342
  // first independent variable has index 7 and the second independent
343
  // variable has index 1.
344
  if (num_independents != (data_dimension - 1)) {
376✔
345
    CMLMessage::error(
3✔
346
      __FILE__,__LINE__,"Initialization error:\n",
347
      "The number of independent variables(",num_independents,
348
      ") is inconsistent with the\n"
349
      "dimension of the data table for each dependent variable (",
350
      data_dimension - 1,").\n"
351
      "Initialization failed.\n");
352
    return false;
3✔
353
  }
354
  // Now lock down the number of independents so we don't have to do that
355
  // again.
356
  for (size_t ii = 0; ii < num_independents; /*incremented internally*/) {
946✔
357
    // elements of independents comprise a pair:
358
    //   {pointer to the IndependentVariable, method used for that variable}
359
    // independents[ii].first is the pointer to the TableIndependentVariable.
360

361
    // Check for NULL pointer:
362
    if (independents[ii].first == nullptr) {
579✔
363
      CMLMessage::error(
1✔
364
        __FILE__,__LINE__,"Initialization error:\n",
365
        "The independent variable at location ",ii," in the independents vector"
366
        " has not been populated.\nThe pointer to it is still NULL.\n"
367
        "Initialization failed.\n");
368
      return false;
1✔
369
    }
370
    // Check that the TableIndependentVariable has data
371
    if (!independents[ii].first->is_data_loaded()) {
578✔
372
      CMLMessage::error(
2✔
373
        __FILE__,__LINE__,"Initialization error:\n",
374
        "The independent array at index ",ii," (named ",
375
        independents[ii].first->get_name_char(),") has not had data loaded."
2✔
376
        "\nInitialization failed.\n");
377
      return false;
2✔
378
    }
379
    // Check that the size of the TableIndependentVariable's data set matches
380
    // the size of the corresponding dimension of output data.
381
    if (independents[ii].first->get_size() !=
576✔
382
                         size_of_dimension.at(ii + 1)) {
576✔
383
      CMLMessage::error(
3✔
384
        __FILE__,__LINE__,"Initialization error:\n",
385
        "The size of independent variable[",ii,"] (",
386
        independents[ii].first->get_size(),") is inconsistent with the\n"
3✔
387
        "size of dimension[",ii+1,"] (",
388
        size_of_dimension.at(ii + 1),") of the data table.\n"
3✔
389
        "Initialization failed.\n");
390
      return false;
3✔
391
    }
392
    // Any independents with a single data point serve no purpose, they can
393
    // be removed from the table at this point.
394
    // First apply a sanity check.  This should be unreachable.  For this to be
395
    // hit, the size of the independent must have changed since dependent data
396
    // was loaded, but in that case the previous test would have picked it up.
397
    if (independents[ii].first->get_size() <= 1) {
573✔
398
      if (num_data_elements_per_increment_of_index[ii] !=
8✔
399
          num_data_elements_per_increment_of_index[ii+1]) {
4✔
400
        CMLMessage::fail(
×
401
          __FILE__,__LINE__,"Internal error:\n",
402
          "Failed sanity check on the structure of the "
403
          "num_data_elements_per_increment_of_index\n"
404
          "array for index ",ii,".\n"
405
          "Before removing this index, needed to check that it\n"
406
          "had not been wrongly factored into the indexing.\n"
407
          "This independent is registered with a single data point, but the\n"
408
          "indexing suggests that it should have ",
409
            num_data_elements_per_increment_of_index[ii] /
×
410
            num_data_elements_per_increment_of_index[ii+1],
×
411
          " data-points.\n");
412
      }
413
      // Sanity check passed, go ahead and remove it.
414
      CMLMessage::warn(
4✔
415
        __FILE__,__LINE__,"Single-valued Independent:\n",
416
        "At index ",ii,", the independent variable has only 1 data value.\n"
417
        "There is nothing to interpolate or look up for this value; it is\n"
418
        "being removed from the set of independents.\n");
419
      independents.erase(independents.begin()+ii);
4✔
420
      size_of_dimension.erase(size_of_dimension.begin()+ii+1);
4✔
421
      num_data_elements_per_increment_of_index.erase(
8✔
422
            num_data_elements_per_increment_of_index.begin()+ii+1);
8✔
423
      num_independents = independents.size();
4✔
424
      // Notes -
425
      //   - no increment, hold ii steady for next value which is now at
426
      //     the same index.
427
      //   - data_dimension also changes with the erasure of a dimension,
428
      //     but it isn't used again so does not need to be changed.
429
      //     data_dimension = size_of_dimension.size();
430
    }
431
    else { // with at least 1 independent, the lookup/interpolation process
432
           // will involve real calculations.
433
      ++ii; // move onto next item in vector.
569✔
434
      trivial_case = false;
569✔
435
    }
436
  }
437

438

439
  // If all inputs are 1-element in size, there is nothing to interpolate or
440
  // lookup.  Set the output once to the only data point for that variable and
441
  // leave it there.  Initialization of lookup is incomplete, updates will not
442
  // proceed.
443
  if (trivial_case) {
367✔
444
    generate_trivial_output();
2✔
445
  }
446
  else {
447
    configure_support_arrays();
365✔
448
  }
449
  initialized = true;
367✔
450
  return true;
367✔
451
}
452

453
/*****************************************************************************
454
update
455
Purpose:(The master executive call to generate the data)
456
*****************************************************************************/
457
bool
458
GenericMultiInputTable::update()
11,410✔
459
{
460
  if (!initialized) {
11,410✔
461
    CMLMessage::error(
7✔
462
      __FILE__, __LINE__, "Lookup error.\n",
463
      "Table has not been initialized; cannot proceed to perform interpolation "
464
      "or lookup.\n");
465
    return false;
7✔
466
  }
467
  if (trivial_case) { // nothing to do, already solved.
11,403✔
468
    // call this because something outside the table model could have changed
469
    // the value of any dependent variables.
470
    generate_trivial_output();
1✔
471
    return true;
1✔
472
  }
473

474
  generate_base_values();
11,402✔
475

476
  return generate_output();
11,402✔
477
}
478

479
/*****************************************************************************
480
generate_output
481
Purpose:(Interpolate between the various interpolation points.)
482
Notes: (( In the case that all independent variables are being interpreted
483
          as discrete values, there is no interpolation to conduct.
484
          In this case, the data_point_weight and data_point_index vectors
485
          are both limited to a single data point with a weight of 1.0.)
486
        ( In the case that all independent variables are being interpreted
487
          as continuous variables, there are 2^n interpolation points)
488
        ( In the case of a mix of discrete and continuous treatments, there
489
          will be 2^p interpolation points, where p = the number of continuous
490
          variables.  The (n-p) discrete variables will all collapse to a
491
          single combination, used at all 2^p interpolation points.)
492
       )
493
*****************************************************************************/
494
bool
495
GenericMultiInputTable::generate_output()
6,642✔
496
{
497
  // Run prechecks, abort output generation if prechecks fail.
498
  if (!precheck_output()) {
6,642✔
499
    return false;
6✔
500
  }
501

502
  // Each dependent variable has an identically-sized data table embedded
503
  // within the full data table.
504
  // For each dependent variable, access the same relative data set,
505
  // multiplying each of these raw data values by its weighting;
506
  // increment them to produce the total output for that dependent variable.
507

508
  const size_t num_data_points_interp = data_point_weight.size();
6,636✔
509
  size_t index_basis = 0;// index_basis marks the first data point
6,636✔
510
                         // for each dependent variable
511

512
  // For each dependent variable:
513
  DoublePtrVec::iterator out_iterator = output.begin();
6,636✔
514
  for (; out_iterator != output.end(); ++out_iterator) {
18,018✔
515
    (**out_iterator) = 0.0; // initialize output value to 0.0 for increments
11,382✔
516

517
    // For each of the 2^n interpolation points
518
    for (size_t jj = 0; jj < num_data_points_interp; ++jj) {
46,433✔
519
      //calculate the correct index in the data array.
520
      //  NOTE - the loop is over the size of the data_point_weight
521
      //         but the index is also used to access data_point_index.
522
      //         The sizes of the two vectors have been confirmed
523
      //         to match in precheck_output().
524
      const size_t data_index = index_basis + data_point_index[jj];
35,051✔
525
      (**out_iterator) += data_point_weight[jj] * data[data_index];
35,051✔
526
    }
527
    // Move on to the next dependent variable.
528
    index_basis +=  num_data_elements_per_increment_of_index[0];
11,382✔
529
  }
530
  return true;
6,636✔
531
}
532

533
/*****************************************************************************
534
precheck_output
535
Purpose:(Checks that should be made for all inherited versions of
536
         generate_output)
537
*****************************************************************************/
538
bool
539
GenericMultiInputTable::precheck_output()
6,736✔
540
{
541
  // Going to be looping through both the data_point_weight and
542
  // data_point_index STL-vectors.  These are always assigned to the same
543
  // size, and there is no reason they would not be the same size.  But check
544
  // it anyway in case the code gets modified at a later date.
545
  if (data_point_index.size() != data_point_weight.size()) {
6,736✔
546
    CMLMessage::error(
×
547
      __FILE__,__LINE__,"Internal error:\n",
548
      "The sizes of the data_point_weight and data_point_index vectors "
549
      "differ.\nThey should always be identical, something has gone wrong\n"
550
      "in the assignment to these two vectors.\n"
551
      "Continuing runs the risk of accessing data out of bounds.\n"
552
      "Aborting table-lookup\n");
553
    return false;
×
554
  }
555

556
  // Check to make sure that if one of the independent variables had its data
557
  // reloaded, the new data was the correct size. Otherwise, we may attempt to
558
  // access data outside the storage of the table in generate_output().
559
  const size_t num_independents = independents.size();
6,736✔
560
  for (size_t ii = 0; ii < num_independents; ++ii) {
24,400✔
561
    if (independents[ii].first->get_size() != size_of_dimension[ii + 1]) {
17,670✔
562
      CMLMessage::error(
12✔
563
        __FILE__,__LINE__,"Internal error:\n",
564
        "The size of independent variable[",ii,"] (",
565
        independents[ii].first->get_size(),") is inconsistent with the\n"
6✔
566
        "size of dimension[",ii+1,"] (",
567
        size_of_dimension[ii + 1],") of the data table.\n");
6✔
568
      return false;
6✔
569
    }
570
  }
571
  return true;
6,730✔
572
}
573

574
/*****************************************************************************
575
generate_trivial_output
576
Purpose:(Extract only the basic data without any interpolation.
577
         Called only as a result of a failed initialization process when it
578
         has been identified that all of the independent variables have only
579
         1 value, and the data array contains only 1 value for each output
580
         variable.)
581
*****************************************************************************/
582
void
583
GenericMultiInputTable::generate_trivial_output()
3✔
584
{
585

586
  if (!output_ptrs_set) {
3✔
587
    // NOTE this is a safety check, it may not be reachable.
588
    // This method is called from update, which requires initialization, which
589
    //  requires certain configuration checks.  To get here, the model would have
590
    //  had to pass through initialization without having gone through
591
    //  add_dependents or populate_output (both set output_ptrs_set = true),
592
    //  but initialization cannot succeed if the output vector is empty and those
593
    //  are the only ways to populate the output vector.
594
    CMLMessage::error(
×
595
      __FILE__,__LINE__,"generate output error:\n",
596
     "The output pointers have not been set, data has nowhere to go.\n");
597
    return;
×
598
  }
599

600
  for (size_t ii = 0; ii < size_of_dimension.size(); ++ii) {
6✔
601
    *output[ii] = data[ii];
3✔
602
  }
603
}
604

605
/*****************************************************************************
606
populate_output
607
Purpose:(Store off a collection of output variables)
608
*****************************************************************************/
609
void
610
GenericMultiInputTable::populate_output(
305✔
611
       const DoublePtrVec &var_ptr_list)
612
{
613
  if (output_ptrs_set) {
305✔
614
    CMLMessage::warn(
5✔
615
      __FILE__,__LINE__,"Construction error:\n",
616
      "The output pointers have already been set.\n"
617
      "This action will remove all current output pointers.\n");
618
  }
619

620
  const size_t num_vars = var_ptr_list.size();
305✔
621
  if (!num_vars) {
305✔
622
    CMLMessage::fail(
4✔
623
      __FILE__,__LINE__,"Construction error:\n",
624
     "There should be at least 1 output for each table.  A zero-sized vector\n"
625
     "was passed in for configuring table outputs.\n");
626
  }
627

628
  // Check the dependent-variable pointers for NULL; if they all pass,
629
  // record them permanently in the output vector.
630
  for (size_t ii = 0; ii < num_vars; ++ii) {
1,610✔
631
    if (var_ptr_list[ii] == nullptr) {
1,307✔
632
      CMLMessage::fail(
4✔
633
        __FILE__,__LINE__,"Construction error:\n",
634
        "Table must be provided with pointers to the variables to be "
635
        "populated.  'nullptr' is not a valid target.\n");
636
    }
637
  }
638
  // All outputs are legit.  Assign them and record that they are legit so
639
  // that we don't have to keep testing them all.
640
  output = var_ptr_list;
303✔
641
  output_ptrs_set = true;
303✔
642
}
303✔
643

644
/*****************************************************************************
645
copy_data
646
Purpose:(configures the data, allocating the necessary blocks)
647
*****************************************************************************/
648
bool
649
GenericMultiInputTable::copy_data(
337✔
650
           const double *data_in)
651
{
652
  if (data_in == nullptr) {
337✔
653
    CMLMessage::error(
1✔
654
      __FILE__,__LINE__,"Table data-load error\n",
655
      "No data is given for the data-load; its pointer is NULL.\n"
656
      "Without data, this table cannot function.\n"
657
      "Aborting table configuration.\n");
658
    return false;
1✔
659
  }
660
  // Configure internal data structure, abort on error
661
  size_t total_data_elements = configure_internal_data_structure();
336✔
662
  if (total_data_elements == 0) {
336✔
663
    CMLMessage::error(
1✔
664
      __FILE__,__LINE__,"Table data-load error\n",
665
      "The internal data structure contains no elements."
666
      "Without data, this table cannot function.\n"
667
      "Aborting table configuration.\n");
668
    return false;
1✔
669
  }
670

671
  // Now we have the total number of data elements that should be contained in
672
  // the data_in array.  It should be equal to the product of the multiple
673
  // dimensional sizes.
674
  data.resize(total_data_elements);
335✔
675
  data.assign(data_in, data_in+total_data_elements);
335✔
676
  data_loaded = true;
335✔
677
  return true;
335✔
678
}
679
/****************************************************************************/
680
bool
681
GenericMultiInputTable::copy_data(
205✔
682
           const DoubleVec & data_in)
683
{
684
  // Configure internal data structure, abort on error
685
  size_t total_data_elements = configure_internal_data_structure();
205✔
686
  if (total_data_elements == 0) {
205✔
687
    CMLMessage::error(
1✔
688
      __FILE__,__LINE__,"Table data-load error\n",
689
      "The internal data structure contains no elements."
690
      "Without data, this table cannot function.\n"
691
      "Aborting table configuration.\n");
692
    return false;
1✔
693
  }
694

695
  if (data_in.size() != total_data_elements) {
204✔
696
    CMLMessage::error(
2✔
697
      __FILE__,__LINE__,"Table data-load error\n",
698
      "Size of the incoming data is inconsistent with the specified "
699
      "dimensions.\n"
700
      "-- Incoming data has ", data_in.size(), " elements.\n"
1✔
701
      "-- Specified dimension sizes require ", total_data_elements,
702
      "elements (distributed over ",size_of_dimension.size(), " dimensions.\n"
1✔
703
      "Aborting load-data.\n");
704
    num_data_elements_per_increment_of_index.clear();
1✔
705
    return false;
1✔
706
  }
707
  data = data_in;
203✔
708
  data_loaded = true;
203✔
709
  return true;
203✔
710
}
711

712
/*****************************************************************************
713
configure_internal_data_structure
714
Purpose:(Sets the location of the internal boundaries to divide the 1-d data
715
         storage into its constituent dimensions.)
716
*****************************************************************************/
717
size_t
718
GenericMultiInputTable::configure_internal_data_structure()
541✔
719
{
720
  const size_t data_dimension = size_of_dimension.size();
541✔
721
  // The n-dimensional data array gets loaded up into a 1-d vector.
722
  // It is vital to know how many places in the vector to skip over when the
723
  // index ticks up on an arbitrary dimension.
724
  // When the last index ticks up by 1, the vector advances one position, so
725
  // start with the last index.
726
  // e.g. data[2][4][3] the vector will advance:
727
  //       1 position when the 3rd index upticks
728
  //       3 positions when the 2nd index upticks
729
  //       3*4=12 positions when the 1st index upticks
730
  num_data_elements_per_increment_of_index.resize(data_dimension);
541✔
731
  size_t total_data_elements = 1;
541✔
732
  size_t index = data_dimension;
541✔
733
  // Note: this is done as a do-while loop instead of a for-next loop because
734
  // index is of type size_t which is unsigned. A loop such as:
735
  //     for ( index = data_dimension - 1; index >= 0; index-- )
736
  // would fail on the final pass.
737
  do {
759✔
738
    --index;
1,300✔
739
    num_data_elements_per_increment_of_index[index] = total_data_elements;
1,300✔
740
    if (0 == size_of_dimension[index]) {
1,300✔
741
      CMLMessage::error(
2✔
742
        __FILE__,__LINE__,"Table data-load error\n",
743
        "The dimension-size at index ",index," shoudn't be zero.\n"
744
        "Critical error.  Aborting data configuration.\n");
745
      num_data_elements_per_increment_of_index.clear();
2✔
746
      return 0;
2✔
747
    }
748
    total_data_elements *= size_of_dimension[index];
1,298✔
749
  } while (index>0);
1,298✔
750
  return total_data_elements;
539✔
751
}
752

753
/*****************************************************************************
754
configure_support_arrays
755
Purpose:(configures the data, allocating the necessary blocks)
756
NOTES: called from initialize, after verification that the number of
757
       independents matches with the data tables
758
*****************************************************************************/
759
void
760
GenericMultiInputTable::configure_support_arrays()
365✔
761
{
762
  // The interpolation takes place in n dimensions (where n is the number of
763
  // independent variables), across 2^n data points.  Each of those data
764
  // points will receive a certain weighting, according to the proximity of
765
  // the reference point to the actual data.
766
  //   e.g. in 2-dimensions:
767
  //     in first dimension, the data point is 0.6 through the intervali
768
  //     in second dimension, the data point is 0.3 through the interval
769
  //   data-point 0 (00) gets a weight (0.7)(0.4) = 0.28
770
  //   data point 1 (01) gets a weight (0.3)(0.4) = 0.12
771
  //   data point 2 (10) gets a weight (0.7)(0.6) = 0.42
772
  //   data point 3 (11) gets a weight (0.3)(0.6) = 0.18
773
  // So we need an array to store the computed weights; that array contains
774
  // 2^n data points:
775

776
  // 2^n:
777
  const size_t num_data_points_interp = size_t(1) << independents.size();
365✔
778

779
  data_point_weight.resize(num_data_points_interp);
365✔
780
  data_point_index.resize(num_data_points_interp);
365✔
781
}
365✔
782

783
/*****************************************************************************
784
generate_base_values
785
Purpose:(Uses the independent variable fractions to weight the data points
786
         appropriately.)
787
*****************************************************************************/
788
void
789
GenericMultiInputTable::generate_base_values()
6,642✔
790
{
791
  // First go through the list of the independents, computing how many
792
  // interpolation points are needed for this dependent variable.
793
  size_t num_independents_interp = 0;
6,642✔
794
  for( IndepPair independent : independents) {
24,230✔
795
    if (  independent.second == TableIndependentVariable::Interp &&
30,328✔
796
         !independent.first->is_off_table()) {
12,740✔
797
      ++num_independents_interp;
10,180✔
798
    }
799
  }
800
  // Note - carry on even if there is nothing to interpolate.  It is still
801
  //        necessary to generate the index of the single data point used
802
  //        for a simple lookup.
803

804

805
  // We have n independent variables, some subset m of which are to be used
806
  // for interpolation, and the remainder (i.e. n-m) are direct lookups.
807
  // (Note: m in range [0,n], m=num_independents_interp).
808
  // Have to interpolate between 2^m points, distributed across n dimensions
809
  // of data.
810
  // We need to get the index of each of those data points, and the weight
811
  // that is to be applied to each for taking the average of them.
812
  // Start by populating the STL-containers with the correct number of
813
  // points.
814
  const size_t num_data_points_interp = size_t(1) << num_independents_interp;
6,642✔
815
  data_point_weight.assign(num_data_points_interp, 1.0);
6,642✔
816
  data_point_index.assign(num_data_points_interp, 0);
6,642✔
817

818
  size_t dwell = 1;
6,642✔
819
  // start on the dimension=1, i.e. the first independent variable
820
  // Note dimension=0 corresponds to the independnt variables.
821
  // Increment current_dimension as independents are processed.
822
  size_t current_dimension = 1;
6,642✔
823

824
  for( IndepPair independent : independents) {
24,230✔
825
    TableIndependentVariable & TIV = *independent.first;
17,588✔
826
    // Trivial case, the independent variable has 1 (or fewer, if that is
827
    // possible) data point.
828
    // NOTE - this should not be possible; such an independent should have
829
    //        already been stripped from the table at initialization.
830
    if (TIV.get_size() <= 1) {
17,588✔
831
      // No action; index for this independent is zero so there is nothing to
832
      // add to the array index and cannot interpolate so no weighting to
833
      // include.
834
      continue;
×
835
    }
836

837
    const size_t index_multiplier =
838
          num_data_elements_per_increment_of_index[current_dimension];
17,588✔
839
    size_t index = TIV.get_index();
17,588✔
840
    bool index_processed = false;
17,588✔
841

842
    // Switch on the Lookup Method for how THIS table uses each independent:
843
    // Note - this is a table-specific value; a single independent may be used
844
    //        in different ways in different tables.
845
    switch (independent.second) {
17,588✔
846
    case TableIndependentVariable::Interp:
12,740✔
847
      {
848
      // Only interpolate if there are at least 2 points (already checked)
849
      // and if the independent variable is still in its table.
850
      // Otherwise, just use the end-point data at the default index
851
      if( TIV.is_off_table())  {
12,740✔
852
        break; // use default index.  No other actions
2,560✔
853
      }
854

855
      // else: special case -- add to the interpolation.
856
      if (dwell >= num_data_points_interp) {
10,180✔
857
        CMLMessage::fail(
×
858
          __FILE__,__LINE__,"Data Processing error.\n",
859
          "The value `dwell` (",dwell,") is too large.\n"
860
          "It can be at most one-half of the number of interpolation points (",
861
          num_data_points_interp, ").\n"
862
          "This message is a fail-safe.  It should never be seen.\n");
863
      }
864
      // For each of the interpolation points (jj), apply frac weight to the
865
      //   upper index and (1-frac) weight to the lower index for the
866
      //   independent variable currently being processed.
867
      // Alternate between lower and upper indices according to which
868
      //   independent variable is being worked.
869
      // The overall goal here is to apply an identifying pattern to the
870
      //   points in the interpolation-point set, processing one independent
871
      //   variable at a time.
872
      //   Apply the pattern (l, u, l, u, l, ...) during the 1st independent
873
      //   next, go with     (l, l, u, u, l, ...) for the 2nd
874
      //   then go with      (l, l, l, l, u, ...) for the 3rd
875
      //   etc.
876
      //   to produce a unique pattern of {u,l} on each of the interpolation
877
      //   points: {lll, ull, lul, uul, llu, ulu, luu, uuu}
878
      // "dwell" provides the dwell-time on each index for the given
879
      //   independent variable.
880
      // Notes:
881
      // - fraction measures the "distance" from index, so if fraction = 0.0,
882
      //   all the weight (1.0) should be applied to index and none (0.0) to
883
      //   index+1.
884
      //   Hence the weighting for the point at index+1 is fraction, and the
885
      //   weighting for the point at index is 1-frac.
886
      // - The data_point_index value is the location in the data STL-vector
887
      //   where the interpolation data-value can be found (for each of the
888
      //   interpolation points). Because the data vector contains data asa
889
      //   function of ALL independent variables (even the ones using a "lookup"
890
      //   rather than an "interpolation"), this index is not finalized for any
891
      //   data point until all independent variables have processed.
892
      //   The index value is incremented as each independent gets processed.
893
      double weight_upper  = TIV.fraction;
10,180✔
894
      double weight = 1-weight_upper;
10,180✔
895
      size_t index_upper = index+1;
10,180✔
896
      for (size_t jj=0; jj<num_data_points_interp; ) {
25,964✔
897
        // Apply the values from the lower index, applying the parameters to
898
        // "dwell" points before moving to the upper index.
899
        // Note:  - jj is incremented in this (and the next) embedded loop,
900
        //          not in the jj for-loop statement
901
        for (size_t kk=0; kk<dwell; ++kk, ++jj) {
37,753✔
902
          data_point_weight[jj] *= weight;
21,969✔
903
          data_point_index[jj] += index * index_multiplier;
21,969✔
904
        }
905
        // apply the values from the upper index.  After another "dwell"
906
        // points, test whether all points are done and go back to the lower
907
        // index if not.
908
        for (size_t kk=0; kk<dwell; ++kk, ++jj) {
37,753✔
909
          data_point_weight[jj] *= weight_upper;
21,969✔
910
          data_point_index[jj] += index_upper * index_multiplier;
21,969✔
911
        }
912
      }
913
      dwell *= 2; // set for next iterating dimension
10,180✔
914
      index_processed = true;
10,180✔
915
      break;
10,180✔
916
      }
917

918
    case TableIndependentVariable::Prev:
1,212✔
919
      if ( TIV.prox_override) {
1,212✔
920
        index = TIV.index_prox;
44✔
921
      }
922
      break;
1,212✔
923
    case TableIndependentVariable::Next:
1,212✔
924
      // Use prox-index if in proximity
925
      if ( TIV.prox_override) {
1,212✔
926
        index = TIV.index_prox;
64✔
927
      }
928
      // increment index if not off the table.
929
      else if( !TIV.is_off_table())  {
1,148✔
930
        ++index;
712✔
931
      }
932
      break;
1,212✔
933
    case TableIndependentVariable::Floor:
1,010✔
934
      if ( TIV.prox_override) {
1,010✔
935
        index = TIV.index_prox;
46✔
936
      }
937
      // if table is decreasing and value is not off table, use the next index
938
      else if( !TIV.is_off_table() &&
1,620✔
939
               !TIV.is_table_increasing()) {
656✔
940
        ++index;
352✔
941
      }
942
      break;
1,010✔
943
    case TableIndependentVariable::Ceil:
1,010✔
944
      if ( TIV.prox_override) {
1,010✔
945
        index = TIV.index_prox;
52✔
946
      }
947
      // if table is increasing and value is not off table, use the next index
948
      else if( !TIV.is_off_table() &&
1,676✔
949
                TIV.is_table_increasing()) {
718✔
950
        ++index;
366✔
951
      }
952
      break;
1,010✔
953
    case TableIndependentVariable::Round:
404✔
954
      if ( TIV.fraction > 0.5) {
404✔
955
        ++index;
88✔
956
      }
957
      break;
404✔
958
    default:
×
959
      // NOTE - this should be unreachable.  The LookupMethod is assigned as
960
      // part of the add_independent method, which requires a valid
961
      // enumeration (or assuumes one if none is provided).  THereafter, that
962
      // value is protected.  This is a fail-safe.
963
      CMLMessage::fail(
×
964
        __FILE__,__LINE__,"Invalid specification\n",
965
        "Specification of LookupMethod is invalid.\n");
966
      break;
×
967
    }
968

969
    // If the data indices have not been adjusted by this independent, do so
970
    // now.
971
    if (!index_processed) {
17,588✔
972
      for (size_t jj=0; jj<num_data_points_interp; ++jj) {
25,278✔
973
        data_point_index[jj] += index * index_multiplier;
17,870✔
974
      }
975
    }
976

977
    ++current_dimension; // Move on to the next independent variable.
17,588✔
978
  } // end for loop through all independents
979
}
6,642✔
980

981
/*****************************************************************************
982
bias_data
983
Purpose:(Bias specified elements in the data array by the specified offset.)
984
*****************************************************************************/
985
void
986
GenericMultiInputTable::bias_data(
40✔
987
        double bias,
988
        size_t ix_start,
989
        size_t ix_stop)
990
{
991
  if (index_checks(ix_start, ix_stop, "bias")) { return; }
40✔
992
  for (size_t ii = ix_start; ii <= ix_stop; ++ii) {
83✔
993
    data[ii] += bias;
44✔
994
  }
995
}
996

997
/*****************************************************************************
998
scale_data
999
Purpose:(scale specified elements in the data array by the specified factor.)
1000
*****************************************************************************/
1001
void
1002
GenericMultiInputTable::scale_data(
3✔
1003
        double scale,
1004
        size_t ix_start,
1005
        size_t ix_stop)
1006
{
1007
  if (index_checks(ix_start, ix_stop, "scale")) { return; }
3✔
1008
  for (size_t ii = ix_start; ii <= ix_stop; ++ii) {
3✔
1009
    data[ii] *= scale;
2✔
1010
  }
1011
}
1012

1013
/*****************************************************************************
1014
index_checks
1015
Purpose:(Index checks common to bias_data and scale_data.)
1016
*****************************************************************************/
1017
bool
1018
GenericMultiInputTable::index_checks(
43✔
1019
        size_t & ix_start,
1020
        size_t & ix_stop,
1021
        std::string func)
1022
{
1023
  // Note - data_loaded implies data.size() > 0.
1024
  if (!data_loaded) {
43✔
1025
    CMLMessage::warn(
2✔
1026
      __FILE__,__LINE__,"Invalid external initialization sequence\n",
1027
      "Call made to ",func," data but data has not yet been loaded.\n"
1028
      "There are no data to ",func,".\n"
1029
      "Check sequencing.\n");
1030
    return true;
2✔
1031
  }
1032
  if (ix_start > ix_stop) {
41✔
1033
    CMLMessage::warn(
2✔
1034
      __FILE__,__LINE__,"Invalid arguments\n",
1035
      "Call made to ",func," data between two indices with the start index (",
1036
      ix_start,")\n"
1037
      "higher than the stop index (",ix_stop,").  This could be an error.\n"
1038
      "Will ",func," the data values between these indices.\n");
1039
    size_t ix_scratch = ix_start;
2✔
1040
    ix_start = ix_stop;
2✔
1041
    ix_stop = ix_scratch;
2✔
1042
  }
1043
  if (ix_stop >= data.size()) {
41✔
1044
    CMLMessage::warn(__FILE__, __LINE__, "Invalid index\n",
2✔
1045
      "Call made to ",func," data with the stop index (",ix_stop,
1046
      ") past the end of the list.\n"
1047
      "Will ",func," all data between the start index and\n"
1048
      "the end of the list.\n");
1049
    if (ix_start >= data.size()) { return true; }
2✔
1050
    ix_stop = data.size() - 1;
1✔
1051
  }
1052
  return false;
40✔
1053
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc