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

NREL / SolTrace / 20379147445

19 Dec 2025 06:34PM UTC coverage: 89.725% (+0.5%) from 89.184%
20379147445

push

github

web-flow
Implement multi-threading in NativeRunner (#91)

* Initial implementation of multi-threading in NativeRunner

* Fix seeding issue; address copilot comments; implement logging

* Add missing header

* Run performance tests only for release build; adjust parameters for cancel multithread test

* Fix clear and setup call ordering in RayData

* Update profile flags; fix printf compiler warning

* Still trying to fix coverage

* Remove multi-threading tests from coverage; remove performance tests for debug builds

* Add missing header

* Increase test coverage; address copilot comments

* Make number of rays traced consistent across number of threads

* Increase time for native runner validation test

430 of 471 new or added lines in 11 files covered. (91.3%)

2 existing lines in 2 files now uncovered.

6104 of 6803 relevant lines covered (89.73%)

7499675.17 hits per line

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

92.45
/coretrace/simulation_runner/native_runner/native_runner.cpp
1

2
#include "native_runner.hpp"
3

4
#include <chrono>
5
#include <exception>
6
#include <map>
7
#include <mutex>
8
#include <thread>
9

10
// SimulationData headers
11
#include "composite_element.hpp"
12
#include "element.hpp"
13
#include "simulation_parameters.hpp"
14
#include "simulation_data.hpp"
15
#include "simulation_data_export.hpp"
16

17
// NativeRunner headers
18
#include "native_runner_types.hpp"
19
#include "trace.hpp"
20

21
namespace SolTrace::NativeRunner
22
{
23

24
    NativeRunner::NativeRunner() : SimulationRunner(),
26✔
25
                                   as_power_tower(false),
26✔
26
                                   number_of_threads(1)
26✔
27
    {
28
        this->my_manager = make_thread_manager();
26✔
29
    }
26✔
30

31
    NativeRunner::~NativeRunner()
26✔
32
    {
33
    }
26✔
34

35
    RunnerStatus NativeRunner::initialize()
24✔
36
    {
37
        return RunnerStatus::SUCCESS;
24✔
38
    }
39

40
    RunnerStatus NativeRunner::setup_simulation(const SimulationData *data)
25✔
41
    {
42

43
        RunnerStatus sts;
44

45
        this->tsys.ClearAll();
25✔
46

47
        sts = this->setup_parameters(data);
25✔
48

49
        if (sts == RunnerStatus::SUCCESS)
25✔
50
            sts = this->setup_sun(data);
25✔
51

52
        if (sts == RunnerStatus::SUCCESS)
25✔
53
            sts = this->setup_elements(data);
25✔
54

55
        return sts;
25✔
56
    }
57

58
    RunnerStatus NativeRunner::setup_parameters(const SimulationData *data)
25✔
59
    {
60
        // Get Parameter data
61
        const SimulationParameters &sim_params = data->get_simulation_parameters();
25✔
62
        this->tsys.sim_errors_sunshape = sim_params.include_sun_shape_errors;
25✔
63
        this->tsys.sim_errors_optical = sim_params.include_optical_errors;
25✔
64
        this->tsys.sim_raycount = sim_params.number_of_rays;
25✔
65
        this->tsys.sim_raymax = sim_params.max_number_of_rays;
25✔
66
        this->tsys.seed = sim_params.seed;
25✔
67
        return RunnerStatus::SUCCESS;
25✔
68
    }
69

70
    RunnerStatus NativeRunner::setup_sun(const SimulationData *data)
26✔
71
    {
72
        if (data->get_number_of_ray_sources() > 1)
26✔
73
        {
NEW
74
            throw std::invalid_argument("NativeRunner: Only 1 ray source is supported.");
×
75
        }
76
        else if (data->get_number_of_ray_sources() <= 0)
26✔
77
        {
NEW
78
            throw std::invalid_argument("NativeRunner: Ray source is required.");
×
79
        }
80

81
        ray_source_ptr sun = data->get_ray_source();
26✔
82
        vector_copy(this->tsys.Sun.Origin, sun->get_position());
26✔
83
        this->tsys.Sun.ShapeIndex = sun->get_shape();
26✔
84

85
        // Set sunshape data
86
        switch (sun->get_shape())
26✔
87
        {
88
        case SunShape::GAUSSIAN:
8✔
89
            this->tsys.Sun.Sigma = sun->get_sigma();
8✔
90
            break;
8✔
91
        case SunShape::PILLBOX:
11✔
92
            this->tsys.Sun.Sigma = sun->get_half_width();
11✔
93
            break;
11✔
94
        case SunShape::LIMBDARKENED:
1✔
95
            this->tsys.Sun.MaxAngle = 4.65; // [mrad]
1✔
96
            this->tsys.Sun.MaxIntensity = 1.0;
1✔
97
            break;
1✔
98
        case SunShape::BUIE_CSR:
1✔
99
        {
100
            this->tsys.Sun.MaxAngle = 43.6; // [mrad]
1✔
101
            this->tsys.Sun.MaxIntensity = 1.0;
1✔
102
            double kappa, gamma;
103
            sun->calculate_buie_parameters(kappa, gamma);
1✔
104
            this->tsys.Sun.buie_kappa = kappa;
1✔
105
            this->tsys.Sun.buie_gamma = gamma;
1✔
106
            break;
1✔
107
        }
108
        case SunShape::USER_DEFINED:
4✔
109
        {
110
            std::vector<double> angle, intensity;
4✔
111
            sun->get_user_data(angle, intensity);
4✔
112
            int npoints = angle.size();
4✔
113

114
            // Set user data
115
            this->tsys.Sun.MaxAngle = 0;
4✔
116
            this->tsys.Sun.MaxIntensity = 0;
4✔
117

118
            this->tsys.Sun.SunShapeAngle.resize(2 * npoints - 1);
4✔
119
            this->tsys.Sun.SunShapeIntensity.resize(2 * npoints - 1);
4✔
120

121
            for (int i = 0; i < npoints; i++)
92✔
122
            {
123
                this->tsys.Sun.SunShapeAngle[npoints + i - 1] = angle[i];
88✔
124
                this->tsys.Sun.SunShapeIntensity[npoints + i - 1] = intensity[i];
88✔
125

126
                if (angle[i] > this->tsys.Sun.MaxAngle)
88✔
127
                    this->tsys.Sun.MaxAngle = angle[i];
84✔
128
                if (intensity[i] > this->tsys.Sun.MaxIntensity)
88✔
129
                    this->tsys.Sun.MaxIntensity = intensity[i];
4✔
130
            }
131
            // fill negative angle side of array -> I don't think we need this.
132
            // for (int i = 0; i < npoints - 1; i++)
133
            //{
134
            //    this->tsys.Sun.SunShapeAngle[i] = -angle[npoints - i - 1];
135
            //    this->tsys.Sun.SunShapeIntensity[i] = intensity[npoints - i - 1];
136
            //}
137
            break;
4✔
138
        }
4✔
139
        default:
1✔
140
            if (data->get_simulation_parameters().include_sun_shape_errors)
1✔
141
            {
NEW
142
                throw std::invalid_argument("Unrecognized sun shape.");
×
143
            }
144
            break;
1✔
145
        }
146

147
        return RunnerStatus::SUCCESS;
26✔
148
    }
26✔
149

150
    RunnerStatus NativeRunner::setup_elements(const SimulationData *data)
25✔
151
    {
152
        // TODO: Improve error messages from this function.
153

154
        RunnerStatus sts = RunnerStatus::SUCCESS;
25✔
155
        auto my_map = std::map<int_fast64_t, tstage_ptr>();
25✔
156
        // int_fast64_t current_stage_id = -1;
157
        tstage_ptr current_stage = nullptr;
25✔
158
        int_fast64_t element_number = 1;
25✔
159
        bool element_found_before_stage = false;
25✔
160

161
        if (data->get_number_of_elements() <= 0)
25✔
162
        {
NEW
163
            throw std::invalid_argument("SimulationData has no elements.");
×
164
        }
165

166
        for (auto iter = data->get_const_iterator();
25✔
167
             !data->is_at_end(iter);
19,131✔
168
             ++iter)
19,106✔
169
        {
170
            element_ptr el = iter->second;
19,106✔
171
            if (el->is_enabled() && el->is_stage())
19,106✔
172
            {
173
                tstage_ptr stage = make_tstage(el, this->eparams);
33✔
174
                auto retval = my_map.insert(
33✔
175
                    std::make_pair(el->get_stage(), stage));
66✔
176

177
                // current_stage_id = stage->stage_id;
178

179
                // std::cout << "Created stage " << el->get_stage()
180
                //           << " with " << stage->ElementList.size() << " elements"
181
                //           << std::endl;
182

183
                if (retval.second == false)
33✔
184
                {
185
                    // TODO: Duplicate stage numbers. Need to make an error
186
                    // message.
187
                    sts = RunnerStatus::ERROR;
×
188
                }
189

190
                current_stage = stage;
33✔
191
                element_number = 1;
33✔
192
            }
33✔
193
            else if (el->is_enabled() && el->is_single())
19,073✔
194
            {
195
                if (current_stage == nullptr)
19,065✔
196
                {
197
                    // throw std::runtime_error("No stage to add element to");
198
                    element_found_before_stage = true;
81✔
199
                    continue;
81✔
200
                }
201
                else if (el->get_stage() != current_stage->stage_id)
18,984✔
202
                {
203
                    throw std::runtime_error(
204
                        "Element does not match current stage");
×
205
                }
206

207
                telement_ptr elem = make_telement(iter->second,
37,968✔
208
                                                  element_number,
209
                                                  this->eparams);
18,984✔
210
                ++element_number;
18,984✔
211
                current_stage->ElementList.push_back(elem);
18,984✔
212
            }
18,984✔
213
        }
19,106✔
214

215
        if (my_map.size() != 0 && element_found_before_stage)
25✔
216
        {
217
            throw std::runtime_error("Element found without a stage");
×
218
        }
219

220
        if (my_map.size() == 0)
25✔
221
        {
222
            // No stage elements found in the passed in data. However,
223
            // the runner requires stages. So make a single stage
224
            // and put everything there. Note that the coordinates are
225
            // set to correspond to global coordinates. This is necessary
226
            // so that the element coordinate setup in make_element are
227
            // correct.
228
            int_fast64_t element_number = 1;
9✔
229
            auto stage = make_tstage(this->eparams);
9✔
230
            stage->ElementList.reserve(data->get_number_of_elements());
9✔
231
            for (auto iter = data->get_const_iterator();
9✔
232
                 !data->is_at_end(iter);
97✔
233
                 ++iter)
88✔
234
            {
235
                element_ptr el = iter->second;
88✔
236
                if (el->is_enabled() && el->is_single())
88✔
237
                {
238
                    telement_ptr tel = make_telement(el,
239
                                                     element_number,
240
                                                     this->eparams);
81✔
241
                    stage->ElementList.push_back(tel);
81✔
242
                    ++element_number;
81✔
243
                }
81✔
244
            }
88✔
245
            my_map.insert(std::make_pair(0, stage));
9✔
246
        }
9✔
247

248
        // std::map (according to the documentation) is automatically
249
        // ordered by the keys so inserting into a map will sort the stages
250
        // and we can just transfer the pointers, in order, to the StageList
251
        // simply by pulling them out of the map.
252
        int_fast64_t last_stage_id = -1;
25✔
253
        for (auto iter = my_map.cbegin();
25✔
254
             iter != my_map.cend();
67✔
255
             ++iter)
42✔
256
        {
257
            assert(last_stage_id < iter->first);
42✔
258
            last_stage_id = iter->first;
42✔
259
            this->tsys.StageList.push_back(iter->second);
42✔
260
        }
261

262
        if (sts == RunnerStatus::SUCCESS)
25✔
263
        {
264
            // std::cout << "Setting ZAperture..." << std::endl;
265
            // Compute and set ZAperture field in each element
266
            bool success = set_aperture_planes(&this->tsys);
25✔
267
            sts = success ? RunnerStatus::SUCCESS : RunnerStatus::ERROR;
25✔
268
        }
269

270
        return sts;
25✔
271
    }
25✔
272

273
    RunnerStatus NativeRunner::update_simulation(const SimulationData *data)
×
274
    {
275
        // TODO: Do a more efficient implementation of this?
276
        this->tsys.ClearAll();
×
277
        this->setup_simulation(data);
×
278
        return RunnerStatus::SUCCESS;
×
279
    }
280

281
    RunnerStatus NativeRunner::run_simulation()
25✔
282
    {
283
        if (this->seeds.empty() ||
25✔
NEW
284
            this->seeds.size() != this->number_of_threads)
×
285
        {
286
            this->seeds.clear();
25✔
287
            for (unsigned k = 0; k < this->number_of_threads; ++k)
50✔
288
            {
289
                this->seeds.push_back(this->tsys.seed + 123 * k);
25✔
290
            }
291
        }
292
        else
293
        {
294
            ; // Intentional no-op
295
        }
296

297
        RunnerStatus sts = trace_native(
25✔
298
            this->my_manager,
25✔
299
            &this->tsys,
300
            this->seeds,
25✔
301
            this->number_of_threads,
302
            this->tsys.sim_raycount,
25✔
303
            this->tsys.sim_raymax,
25✔
304
            this->tsys.sim_errors_sunshape,
25✔
305
            this->tsys.sim_errors_optical,
25✔
306
            this->as_power_tower);
25✔
307

308
        return sts;
25✔
309
    }
310

311
    RunnerStatus NativeRunner::status_simulation(double *progress)
2✔
312
    {
313
        return this->my_manager->status(progress);
2✔
314
    }
315

316
    RunnerStatus NativeRunner::cancel_simulation()
1✔
317
    {
318
        // TODO: Should this have some sort of wait here for the termination?
319
        this->my_manager->cancel();
1✔
320
        return this->my_manager->status();
1✔
321
    }
322

323
    RunnerStatus NativeRunner::report_simulation(SolTrace::Result::SimulationResult *result,
7✔
324
                                                 int level)
325
    {
326
        RunnerStatus retval = RunnerStatus::SUCCESS;
7✔
327

328
        const TSystem *sys = this->get_system();
7✔
329
        // const TRayData ray_data = sys->AllRayData;
330
        const TRayData ray_data = sys->RayData;
7✔
331
        std::map<unsigned int, SolTrace::Result::ray_record_ptr> ray_records;
7✔
332
        std::map<unsigned int, SolTrace::Result::ray_record_ptr>::iterator iter;
7✔
333
        uint_fast64_t ndata = ray_data.Count();
7✔
334

335
        bool sts;
336
        Vector3d point, cosines;
7✔
337
        int element;
338
        int stage;
339
        uint_fast64_t raynum;
340

341
        telement_ptr el = nullptr;
7✔
342
        element_id elid;
343
        SolTrace::Result::ray_record_ptr rec = nullptr;
7✔
344
        SolTrace::Result::interaction_ptr intr = nullptr;
7✔
345
        SolTrace::Result::RayEvent rev;
346

347
        // std::cout << "Num Events: " << ndata << std::endl;
348

349
        for (uint_fast64_t ii = 0; ii < ndata; ++ii)
302,152✔
350
        {
351
            sts = ray_data.Query(ii,
302,145✔
352
                                 point.data,
353
                                 cosines.data,
354
                                 &element,
355
                                 &stage,
356
                                 &raynum,
357
                                 &rev);
358

359
            if (!sts)
302,145✔
360
            {
361
                retval = RunnerStatus::ERROR;
×
362
                break;
×
363
            }
364

365
            // std::cout << "ii: " << ii
366
            //           << "\npoint: " << point
367
            //           << "\ndirection: " << cosines
368
            //           << "\nelement: " << element
369
            //           << "\nstage: " << stage
370
            //           << "\nraynum: " << raynum
371
            //           << "\nevent: " << ray_event_string(rev)
372
            //           << std::endl;
373

374
            iter = ray_records.find(raynum);
302,145✔
375
            if (iter == ray_records.end())
302,145✔
376
            {
377
                rec = SolTrace::Result::make_ray_record(raynum);
100,010✔
378
                result->add_ray_record(rec);
100,010✔
379
                ray_records[raynum] = rec;
100,010✔
380
                assert(rev == SolTrace::Result::RayEvent::CREATE);
100,010✔
381
            }
382
            else
383
            {
384
                rec = iter->second;
202,135✔
385
            }
386

387
            if (element > 0)
302,145✔
388
            {
389
                el = sys->StageList[stage - 1]->ElementList[element - 1];
186,310✔
390
                elid = el->sim_data_id;
186,310✔
391
            }
392
            else
393
            {
394
                elid = element;
115,835✔
395
            }
396

397
            intr = make_interaction_record(elid, rev, point, cosines);
302,145✔
398
            rec->add_interaction_record(intr);
302,145✔
399
        }
400

401
        return retval;
7✔
402
    }
7✔
403

404
    bool NativeRunner::set_aperture_planes(TSystem *tsys)
25✔
405
    {
406
        bool retval;
407

408
        for (auto iter = tsys->StageList.cbegin();
25✔
409
             iter != tsys->StageList.cend();
67✔
410
             ++iter)
42✔
411
        {
412
            retval = this->set_aperture_planes(*iter);
42✔
413
            if (!retval)
42✔
414
                break;
×
415
        }
416

417
        return retval;
25✔
418
    }
419

420
    bool NativeRunner::set_aperture_planes(tstage_ptr stage)
42✔
421
    {
422
        bool retval;
423

424
        for (auto eiter = stage->ElementList.begin();
42✔
425
             eiter != stage->ElementList.end();
19,107✔
426
             ++eiter)
19,065✔
427
        {
428
            retval = aperture_plane(*eiter);
19,065✔
429
            if (!retval)
19,065✔
430
                break;
×
431
        }
432

433
        return retval;
42✔
434
    }
435

436
    bool NativeRunner::aperture_plane(telement_ptr Element)
19,065✔
437
    {
438
        /*{Calculates the aperture plane of the element in element coord system.
439
        Applicable to rotationally symmetric apertures surfaces with small
440
        curvature: g, s, p, o, c, v, m, e, r, i.
441
          input - Element = Element record containing geometry of element
442
          output -
443
                 - Element.ZAperture  where ZAperture is the distance from
444
                   the origin to the plane.
445
        }*/
446

447
        Element->ZAperture =
38,130✔
448
            Element->icalc->compute_z_aperture(Element->aperture);
19,065✔
449

450
        return true;
19,065✔
451
    }
452

453
} // namespace SolTrace::NativeRunner
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc