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

nasa / cml / 29035259148

09 Jul 2026 04:56PM UTC coverage: 76.355% (-0.6%) from 76.929%
29035259148

Pull #37

github

web-flow
Merge 17f796c88 into fe882fc49
Pull Request #37: Refactor the CMLMessage model for mocking

5967 of 7677 branches covered (77.73%)

Branch coverage included in aggregate %.

81 of 83 new or added lines in 2 files covered. (97.59%)

38 existing lines in 21 files now uncovered.

18017 of 23734 relevant lines covered (75.91%)

99986.59 hits per line

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

88.2
/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: ",
3✔
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(
2✔
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(
1✔
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");
2✔
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"
3✔
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;
6✔
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✔
UNCOV
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(
422
            num_data_elements_per_increment_of_index.begin()+ii+1);
4✔
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
  // However, there does not appear to be a way to fail the prechecks, so this
499
  // is a failsafe.
500
  if (!precheck_output()) {
6,642✔
501
    return false;
×
502
  }
503

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

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

514
  // For each dependent variable:
515
  DoublePtrVec::iterator out_iterator = output.begin();
6,642✔
516
  for (; out_iterator != output.end(); ++out_iterator) {
18,033✔
517
    (**out_iterator) = 0.0; // initialize output value to 0.0 for increments
11,391✔
518

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

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

560
/*****************************************************************************
561
generate_trivial_output
562
Purpose:(Extract only the basic data without any interpolation.
563
         Called only as a result of a failed initialization process when it
564
         has been identified that all of the independent variables have only
565
         1 value, and the data array contains only 1 value for each output
566
         variable.)
567
*****************************************************************************/
568
void
569
GenericMultiInputTable::generate_trivial_output()
3✔
570
{
571

572
  if (!output_ptrs_set) {
3✔
573
    // NOTE this is a safety check, it may not be reachable.
574
    // This method is called from update, which requires initialization, which
575
    //  requires certain configuration checks.  To get here, the model would have
576
    //  had to pass through initialization without having gone through
577
    //  add_dependents or populate_output (both set output_ptrs_set = true),
578
    //  but initialization cannot succeed if the output vector is empty and those
579
    //  are the only ways to populate the output vector.
580
    CMLMessage::error(
×
581
      __FILE__,__LINE__,"generate output error:\n",
582
     "The output pointers have not been set, data has nowhere to go.\n");
583
    return;
×
584
  }
585

586
  for (size_t ii = 0; ii < size_of_dimension.size(); ++ii) {
6✔
587
    *output[ii] = data[ii];
3✔
588
  }
589
}
590

591
/*****************************************************************************
592
populate_output
593
Purpose:(Store off a collection of output variables)
594
*****************************************************************************/
595
void
596
GenericMultiInputTable::populate_output(
305✔
597
       const DoublePtrVec &var_ptr_list)
598
{
599
  if (output_ptrs_set) {
305✔
600
    CMLMessage::warn(
5✔
601
      __FILE__,__LINE__,"Construction error:\n",
602
      "The output pointers have already been set.\n"
603
      "This action will remove all current output pointers.\n");
604
  }
605

606
  const size_t num_vars = var_ptr_list.size();
305✔
607
  if (!num_vars) {
305✔
608
    CMLMessage::fail(
3✔
609
      __FILE__,__LINE__,"Construction error:\n",
610
     "There should be at least 1 output for each table.  A zero-sized vector\n"
611
     "was passed in for configuring table outputs.\n");
612
  }
613

614
  // Check the dependent-variable pointers for NULL; if they all pass,
615
  // record them permanently in the output vector.
616
  for (size_t ii = 0; ii < num_vars; ++ii) {
1,610✔
617
    if (var_ptr_list[ii] == nullptr) {
1,307✔
618
      CMLMessage::fail(
2✔
619
        __FILE__,__LINE__,"Construction error:\n",
620
        "Table must be provided with pointers to the variables to be "
621
        "populated.  'nullptr' is not a valid target.\n");
622
    }
623
  }
624
  // All outputs are legit.  Assign them and record that they are legit so
625
  // that we don't have to keep testing them all.
626
  output = var_ptr_list;
303✔
627
  output_ptrs_set = true;
303✔
628
}
303✔
629

630
/*****************************************************************************
631
copy_data
632
Purpose:(configures the data, allocating the necessary blocks)
633
*****************************************************************************/
634
bool
635
GenericMultiInputTable::copy_data(
337✔
636
           const double *data_in)
637
{
638
  if (data_in == nullptr) {
337✔
639
    CMLMessage::error(
1✔
640
      __FILE__,__LINE__,"Table data-load error\n",
641
      "No data is given for the data-load; its pointer is NULL.\n"
642
      "Without data, this table cannot function.\n"
643
      "Aborting table configuration.\n");
644
    return false;
1✔
645
  }
646
  // Configure internal data structure, abort on error
647
  size_t total_data_elements = configure_internal_data_structure();
336✔
648
  if (total_data_elements == 0) {
336✔
649
    CMLMessage::error(
1✔
650
      __FILE__,__LINE__,"Table data-load error\n",
651
      "The internal data structure contains no elements."
652
      "Without data, this table cannot function.\n"
653
      "Aborting table configuration.\n");
654
    return false;
1✔
655
  }
656

657
  // Now we have the total number of data elements that should be contained in
658
  // the data_in array.  It should be equal to the product of the multiple
659
  // dimensional sizes.
660
  data.resize(total_data_elements);
335✔
661
  data.assign(data_in, data_in+total_data_elements);
335✔
662
  data_loaded = true;
335✔
663
  return true;
335✔
664
}
665
/****************************************************************************/
666
bool
667
GenericMultiInputTable::copy_data(
205✔
668
           const DoubleVec & data_in)
669
{
670
  // Configure internal data structure, abort on error
671
  size_t total_data_elements = configure_internal_data_structure();
205✔
672
  if (total_data_elements == 0) {
205✔
673
    CMLMessage::error(
1✔
674
      __FILE__,__LINE__,"Table data-load error\n",
675
      "The internal data structure contains no elements."
676
      "Without data, this table cannot function.\n"
677
      "Aborting table configuration.\n");
678
    return false;
1✔
679
  }
680

681
  if (data_in.size() != total_data_elements) {
204✔
682
    CMLMessage::error(
1✔
683
      __FILE__,__LINE__,"Table data-load error\n",
684
      "Size of the incoming data is inconsistent with the specified "
685
      "dimensions.\n"
686
      "-- Incoming data has ", data_in.size(), " elements.\n"
1✔
687
      "-- Specified dimension sizes require ", total_data_elements,
688
      "elements (distributed over ",size_of_dimension.size(), " dimensions.\n"
1✔
689
      "Aborting load-data.\n");
690
    num_data_elements_per_increment_of_index.clear();
1✔
691
    return false;
1✔
692
  }
693
  data = data_in;
203✔
694
  data_loaded = true;
203✔
695
  return true;
203✔
696
}
697

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

739
/*****************************************************************************
740
configure_support_arrays
741
Purpose:(configures the data, allocating the necessary blocks)
742
NOTES: called from initialize, after verification that the number of
743
       independents matches with the data tables
744
*****************************************************************************/
745
void
746
GenericMultiInputTable::configure_support_arrays()
365✔
747
{
748
  // The interpolation takes place in n dimensions (where n is the number of
749
  // independent variables), across 2^n data points.  Each of those data
750
  // points will receive a certain weighting, according to the proximity of
751
  // the reference point to the actual data.
752
  //   e.g. in 2-dimensions:
753
  //     in first dimension, the data point is 0.6 through the intervali
754
  //     in second dimension, the data point is 0.3 through the interval
755
  //   data-point 0 (00) gets a weight (0.7)(0.4) = 0.28
756
  //   data point 1 (01) gets a weight (0.3)(0.4) = 0.12
757
  //   data point 2 (10) gets a weight (0.7)(0.6) = 0.42
758
  //   data point 3 (11) gets a weight (0.3)(0.6) = 0.18
759
  // So we need an array to store the computed weights; that array contains
760
  // 2^n data points:
761

762
  // 2^n:
763
  const size_t num_data_points_interp = size_t(1) << independents.size();
365✔
764

765
  data_point_weight.resize(num_data_points_interp);
365✔
766
  data_point_index.resize(num_data_points_interp);
365✔
767
}
365✔
768

769
/*****************************************************************************
770
generate_base_values
771
Purpose:(Uses the independent variable fractions to weight the data points
772
         appropriately.)
773
*****************************************************************************/
774
void
775
GenericMultiInputTable::generate_base_values()
6,642✔
776
{
777
  // First go through the list of the independents, computing how many
778
  // interpolation points are needed for this dependent variable.
779
  size_t num_independents_interp = 0;
6,642✔
780
  for( IndepPair independent : independents) {
24,230✔
781
    if (  independent.second == TableIndependentVariable::Interp &&
30,328✔
782
         !independent.first->is_off_table()) {
12,740✔
783
      ++num_independents_interp;
10,180✔
784
    }
785
  }
786
  // Note - carry on even if there is nothing to interpolate.  It is still
787
  //        necessary to generate the index of the single data point used
788
  //        for a simple lookup.
789

790

791
  // We have n independent variables, some subset m of which are to be used
792
  // for interpolation, and the remainder (i.e. n-m) are direct lookups.
793
  // (Note: m in range [0,n], m=num_independents_interp).
794
  // Have to interpolate between 2^m points, distributed across n dimensions
795
  // of data.
796
  // We need to get the index of each of those data points, and the weight
797
  // that is to be applied to each for taking the average of them.
798
  // Start by populating the STL-containers with the correct number of
799
  // points.
800
  const size_t num_data_points_interp = size_t(1) << num_independents_interp;
6,642✔
801
  data_point_weight.assign(num_data_points_interp, 1.0);
6,642✔
802
  data_point_index.assign(num_data_points_interp, 0);
6,642✔
803

804
  size_t dwell = 1;
6,642✔
805
  // start on the dimension=1, i.e. the first independent variable
806
  // Note dimension=0 corresponds to the independnt variables.
807
  // Increment current_dimension as independents are processed.
808
  size_t current_dimension = 1;
6,642✔
809

810
  for( IndepPair independent : independents) {
24,230✔
811
    TableIndependentVariable & TIV = *independent.first;
17,588✔
812
    // Trivial case, the independent variable has 1 (or fewer, if that is
813
    // possible) data point.
814
    // NOTE - this should not be possible; such an independent should have
815
    //        already been stripped from the table at initialization.
816
    if (TIV.get_size() <= 1) {
17,588✔
817
      // No action; index for this independent is zero so there is nothing to
818
      // add to the array index and cannot interpolate so no weighting to
819
      // include.
820
      continue;
×
821
    }
822

823
    const size_t index_multiplier =
824
          num_data_elements_per_increment_of_index[current_dimension];
17,588✔
825
    size_t index = TIV.get_index();
17,588✔
826
    bool index_processed = false;
17,588✔
827

828
    // Switch on the Lookup Method for how THIS table uses each independent:
829
    // Note - this is a table-specific value; a single independent may be used
830
    //        in different ways in different tables.
831
    switch (independent.second) {
17,588✔
832
    case TableIndependentVariable::Interp:
12,740✔
833
      {
834
      // Only interpolate if there are at least 2 points (already checked)
835
      // and if the independent variable is still in its table.
836
      // Otherwise, just use the end-point data at the default index
837
      if( TIV.is_off_table())  {
12,740✔
838
        break; // use default index.  No other actions
2,560✔
839
      }
840

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

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

955
    // If the data indices have not been adjusted by this independent, do so
956
    // now.
957
    if (!index_processed) {
17,588✔
958
      for (size_t jj=0; jj<num_data_points_interp; ++jj) {
25,278✔
959
        data_point_index[jj] += index * index_multiplier;
17,870✔
960
      }
961
    }
962

963
    ++current_dimension; // Move on to the next independent variable.
17,588✔
964
  } // end for loop through all independents
965
}
6,642✔
966

967
/*****************************************************************************
968
bias_data
969
Purpose:(Bias specified elements in the data array by the specified offset.)
970
*****************************************************************************/
971
void
972
GenericMultiInputTable::bias_data(
40✔
973
        double bias,
974
        size_t ix_start,
975
        size_t ix_stop)
976
{
977
  if (index_checks(ix_start, ix_stop, "bias")) { return; }
40✔
978
  for (size_t ii = ix_start; ii <= ix_stop; ++ii) {
83✔
979
    data[ii] += bias;
44✔
980
  }
981
}
982

983
/*****************************************************************************
984
scale_data
985
Purpose:(scale specified elements in the data array by the specified factor.)
986
*****************************************************************************/
987
void
988
GenericMultiInputTable::scale_data(
3✔
989
        double scale,
990
        size_t ix_start,
991
        size_t ix_stop)
992
{
993
  if (index_checks(ix_start, ix_stop, "scale")) { return; }
3✔
994
  for (size_t ii = ix_start; ii <= ix_stop; ++ii) {
3✔
995
    data[ii] *= scale;
2✔
996
  }
997
}
998

999
/*****************************************************************************
1000
index_checks
1001
Purpose:(Index checks common to bias_data and scale_data.)
1002
*****************************************************************************/
1003
bool
1004
GenericMultiInputTable::index_checks(
43✔
1005
        size_t & ix_start,
1006
        size_t & ix_stop,
1007
        std::string func)
1008
{
1009
  // Note - data_loaded implies data.size() > 0.
1010
  if (!data_loaded) {
43✔
1011
    CMLMessage::warn(
2✔
1012
      __FILE__,__LINE__,"Invalid external initialization sequence\n",
1013
      "Call made to ",func," data but data has not yet been loaded.\n"
1014
      "There are no data to ",func,".\n"
1015
      "Check sequencing.\n");
1016
    return true;
2✔
1017
  }
1018
  if (ix_start > ix_stop) {
41✔
1019
    CMLMessage::warn(
2✔
1020
      __FILE__,__LINE__,"Invalid arguments\n",
1021
      "Call made to ",func," data between two indices with the start index (",
1022
      ix_start,")\n"
1023
      "higher than the stop index (",ix_stop,").  This could be an error.\n"
1024
      "Will ",func," the data values between these indices.\n");
1025
    size_t ix_scratch = ix_start;
2✔
1026
    ix_start = ix_stop;
2✔
1027
    ix_stop = ix_scratch;
2✔
1028
  }
1029
  if (ix_stop >= data.size()) {
41✔
1030
    CMLMessage::warn(__FILE__, __LINE__, "Invalid index\n",
2✔
1031
      "Call made to ",func," data with the stop index (",ix_stop,
1032
      ") past the end of the list.\n"
1033
      "Will ",func," all data between the start index and\n"
1034
      "the end of the list.\n");
1035
    if (ix_start >= data.size()) { return true; }
2✔
1036
    ix_stop = data.size() - 1;
1✔
1037
  }
1038
  return false;
40✔
1039
}
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