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

NREL / SolTrace / 20282790366

16 Dec 2025 09:09PM UTC coverage: 88.351% (-0.8%) from 89.184%
20282790366

Pull #91

github

web-flow
Merge 253307deb into a73a760a4
Pull Request #91: Implement multi-threading in NativeRunner

366 of 468 new or added lines in 11 files covered. (78.21%)

34 existing lines in 6 files now uncovered.

6007 of 6799 relevant lines covered (88.35%)

7475791.81 hits per line

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

92.86
/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(),
25✔
25
                                   as_power_tower(false),
25✔
26
                                   number_of_threads(1)
25✔
27
    {
28
        this->my_manager = make_thread_manager();
25✔
29
    }
25✔
30

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

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

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

43
        RunnerStatus sts;
44

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

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

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

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

55
        return sts;
24✔
56
    }
57

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

71
    RunnerStatus NativeRunner::setup_sun(const SimulationData *data)
25✔
72
    {
73
        // // TODO: This should throw an error...
74
        // // Get RaySource data (this runner assumes there is only the Sun)
75
        // assert(data->get_number_of_ray_sources() == 1);
76

77
        if (data->get_number_of_ray_sources() > 1)
25✔
78
        {
NEW
79
            throw std::invalid_argument("NativeRunner: Only 1 ray source is supported.");
×
80
        }
81
        else if (data->get_number_of_ray_sources() <= 0)
25✔
82
        {
NEW
83
            throw std::invalid_argument("NativeRunner: Ray source is required.");
×
84
        }
85

86
        ray_source_ptr sun = data->get_ray_source();
25✔
87
        vector_copy(this->tsys.Sun.Origin, sun->get_position());
25✔
88
        this->tsys.Sun.ShapeIndex = sun->get_shape();
25✔
89

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

119
            // Set user data
120
            this->tsys.Sun.MaxAngle = 0;
3✔
121
            this->tsys.Sun.MaxIntensity = 0;
3✔
122

123
            this->tsys.Sun.SunShapeAngle.resize(2 * npoints - 1);
3✔
124
            this->tsys.Sun.SunShapeIntensity.resize(2 * npoints - 1);
3✔
125

126
            for (int i = 0; i < npoints; i++)
65✔
127
            {
128
                this->tsys.Sun.SunShapeAngle[npoints + i - 1] = angle[i];
62✔
129
                this->tsys.Sun.SunShapeIntensity[npoints + i - 1] = intensity[i];
62✔
130

131
                if (angle[i] > this->tsys.Sun.MaxAngle)
62✔
132
                    this->tsys.Sun.MaxAngle = angle[i];
59✔
133
                if (intensity[i] > this->tsys.Sun.MaxIntensity)
62✔
134
                    this->tsys.Sun.MaxIntensity = intensity[i];
3✔
135
            }
136
            // fill negative angle side of array -> I don't think we need this.
137
            // for (int i = 0; i < npoints - 1; i++)
138
            //{
139
            //    this->tsys.Sun.SunShapeAngle[i] = -angle[npoints - i - 1];
140
            //    this->tsys.Sun.SunShapeIntensity[i] = intensity[npoints - i - 1];
141
            //}
142
            break;
3✔
143
        }
3✔
144
        default:
1✔
145
            // TODO: add error
146
            break;
1✔
147
        }
148

149
        return RunnerStatus::SUCCESS;
25✔
150
    }
25✔
151

152
    RunnerStatus NativeRunner::setup_elements(const SimulationData *data)
24✔
153
    {
154
        // TODO: Improve error messages from this function.
155

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

163
        if (data->get_number_of_elements() <= 0)
24✔
164
        {
NEW
165
            throw std::invalid_argument("SimulationData has no elements.");
×
166
            // return RunnerStatus::ERROR;
167
        }
168

169
        for (auto iter = data->get_const_iterator();
24✔
170
             !data->is_at_end(iter);
12,845✔
171
             ++iter)
12,821✔
172
        {
173
            element_ptr el = iter->second;
12,821✔
174
            if (el->is_enabled() && el->is_stage())
12,821✔
175
            {
176
                tstage_ptr stage = make_tstage(el, this->eparams);
31✔
177
                auto retval = my_map.insert(
31✔
178
                    std::make_pair(el->get_stage(), stage));
62✔
179

180
                // current_stage_id = stage->stage_id;
181

182
                // std::cout << "Created stage " << el->get_stage()
183
                //           << " with " << stage->ElementList.size() << " elements"
184
                //           << std::endl;
185

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

193
                current_stage = stage;
31✔
194
                element_number = 1;
31✔
195
            }
31✔
196
            else if (el->is_enabled() && el->is_single())
12,790✔
197
            {
198
                if (current_stage == nullptr)
12,782✔
199
                {
200
                    // throw std::runtime_error("No stage to add element to");
201
                    element_found_before_stage = true;
81✔
202
                    continue;
81✔
203
                }
204
                else if (el->get_stage() != current_stage->stage_id)
12,701✔
205
                {
206
                    throw std::runtime_error(
207
                        "Element does not match current stage");
×
208
                }
209

210
                telement_ptr elem = make_telement(iter->second,
25,402✔
211
                                                  element_number,
212
                                                  this->eparams);
12,701✔
213
                ++element_number;
12,701✔
214
                current_stage->ElementList.push_back(elem);
12,701✔
215
            }
12,701✔
216
        }
12,821✔
217

218
        if (my_map.size() != 0 && element_found_before_stage)
24✔
219
        {
220
            throw std::runtime_error("Element found without a stage");
×
221
        }
222

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

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

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

273
        return sts;
24✔
274
    }
24✔
275

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

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

300
        RunnerStatus sts = trace_native(
24✔
301
            this->my_manager,
24✔
302
            &this->tsys,
303
            this->seeds,
24✔
304
            this->number_of_threads,
305
            this->tsys.sim_raycount,
24✔
306
            this->tsys.sim_raymax,
24✔
307
            this->tsys.sim_errors_sunshape,
24✔
308
            this->tsys.sim_errors_optical,
24✔
309
            this->as_power_tower);
24✔
310

311
        return sts;
24✔
312
    }
313

314
    RunnerStatus NativeRunner::status_simulation(double *progress)
2✔
315
    {
316
        return this->my_manager->status(progress);
2✔
317
    }
318

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

326
    RunnerStatus NativeRunner::report_simulation(SolTrace::Result::SimulationResult *result,
6✔
327
                                                 int level)
328
    {
329
        RunnerStatus retval = RunnerStatus::SUCCESS;
6✔
330

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

338
        bool sts;
339
        Vector3d point, cosines;
6✔
340
        int element;
341
        int stage;
342
        uint_fast64_t raynum;
343

344
        telement_ptr el = nullptr;
6✔
345
        element_id elid;
346
        SolTrace::Result::ray_record_ptr rec = nullptr;
6✔
347
        SolTrace::Result::interaction_ptr intr = nullptr;
6✔
348
        SolTrace::Result::RayEvent rev;
349

350
        // std::cout << "Num Events: " << ndata << std::endl;
351

352
        for (uint_fast64_t ii = 0; ii < ndata; ++ii)
272,954✔
353
        {
354
            sts = ray_data.Query(ii,
272,948✔
355
                                 point.data,
356
                                 cosines.data,
357
                                 &element,
358
                                 &stage,
359
                                 &raynum,
360
                                 &rev);
361

362
            if (!sts)
272,948✔
363
            {
364
                retval = RunnerStatus::ERROR;
×
365
                break;
×
366
            }
367

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

377
            iter = ray_records.find(raynum);
272,948✔
378
            if (iter == ray_records.end())
272,948✔
379
            {
380
                rec = SolTrace::Result::make_ray_record(raynum);
90,010✔
381
                result->add_ray_record(rec);
90,010✔
382
                ray_records[raynum] = rec;
90,010✔
383
                assert(rev == SolTrace::Result::RayEvent::CREATE);
90,010✔
384
            }
385
            else
386
            {
387
                rec = iter->second;
182,938✔
388
            }
389

390
            if (element > 0)
272,948✔
391
            {
392
                el = sys->StageList[stage - 1]->ElementList[element - 1];
168,229✔
393
                elid = el->sim_data_id;
168,229✔
394
            }
395
            else
396
            {
397
                elid = element;
104,719✔
398
            }
399

400
            intr = make_interaction_record(elid, rev, point, cosines);
272,948✔
401
            rec->add_interaction_record(intr);
272,948✔
402
        }
403

404
        return retval;
6✔
405
    }
6✔
406

407
    bool NativeRunner::set_aperture_planes(TSystem *tsys)
24✔
408
    {
409
        bool retval;
410

411
        for (auto iter = tsys->StageList.cbegin();
24✔
412
             iter != tsys->StageList.cend();
64✔
413
             ++iter)
40✔
414
        {
415
            retval = this->set_aperture_planes(*iter);
40✔
416
            if (!retval)
40✔
417
                break;
×
418
        }
419

420
        return retval;
24✔
421
    }
422

423
    bool NativeRunner::set_aperture_planes(tstage_ptr stage)
40✔
424
    {
425
        bool retval;
426

427
        for (auto eiter = stage->ElementList.begin();
40✔
428
             eiter != stage->ElementList.end();
12,822✔
429
             ++eiter)
12,782✔
430
        {
431
            retval = aperture_plane(*eiter);
12,782✔
432
            if (!retval)
12,782✔
433
                break;
×
434
        }
435

436
        return retval;
40✔
437
    }
438

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

450
        Element->ZAperture =
25,564✔
451
            Element->icalc->compute_z_aperture(Element->aperture);
12,782✔
452

453
        return true;
12,782✔
454
    }
455

456
} // 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