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

NREL / SolTrace / 19040526713

03 Nov 2025 03:50PM UTC coverage: 90.052% (+0.4%) from 89.643%
19040526713

push

github

web-flow
Merge pull request #77 from NREL/74-fix-parabola-intersection-missed-case

74 fix parabola intersection missed case

959 of 994 new or added lines in 28 files covered. (96.48%)

2 existing lines in 1 file now uncovered.

4508 of 5006 relevant lines covered (90.05%)

10596530.27 hits per line

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

97.96
/coretrace/simulation_data/cst_templates/linear_fresnel.cpp
1
#include "linear_fresnel.hpp"
2

3
#include <stdexcept>
4
#include <sstream>
5

6
#include "aperture.hpp"
7
#include "constants.hpp"
8
#include "element.hpp"
9
#include "surface.hpp"
10

11
#include "cst_templates/utilities.hpp"
12

13
namespace SolTrace::Data
14
{
15

16
    LinearFresnel::LinearFresnel()
15✔
17
        : CompositeElement(),
18
          initialized(false),
15✔
19
          receiver_height(-1.0),
15✔
20
          azimuth(-1.0),
15✔
21
          tilt(-1.0),
15✔
22
          focused_panels(false),
15✔
23
          aperture_size_x(-1.0),
15✔
24
          aperture_size_y(-1.0),
15✔
25
          num_panels_x(-1),
15✔
26
          num_panels_y(-1),
15✔
27
          gap_x(-1.0),
15✔
28
          gap_y(-1.0),
15✔
29
          gap_center(-1.0),
15✔
30
          abs_diameter(-1.0),
15✔
31
          env_diameter(-1.0),
15✔
32
          env_thickness(-1.0),
15✔
33
          // tracking_angle(0.0),
34
          tracking_limit_lower(-180.0),
15✔
35
          tracking_limit_upper(180.0)
15✔
36
    {
37
        this->optics_absorber.set_ideal_absorption();
15✔
38
        this->optics_mirror.set_ideal_reflection();
15✔
39
        this->optics_env_out.set_ideal_transmission();
15✔
40
        this->optics_env_in.set_ideal_transmission();
15✔
41

42
        this->tracking_origin.set_values(1.0, 0.0, 0.0);
15✔
43
        this->rotation_axis.set_values(0.0, 1.0, 0.0);
15✔
44
        this->neutral_normal.set_values(0.0, 0.0, 1.0);
15✔
45
    }
15✔
46

47
    LinearFresnel::~LinearFresnel()
15✔
48
    {
49
        // Clear vectors
50
        this->mirrors.clear();
15✔
51
        this->absorbers.clear();
15✔
52
        this->envelope.clear();
15✔
53

54
        return;
15✔
55
    }
15✔
56

57
    // double LinearFresnel::get_tracking_angle_degrees() const
58
    // {
59
    //     return this->tracking_angle;
60
    // }
61

62
    // double LinearFresnel::get_tracking_angle_radians() const
63
    // {
64
    //     return D2R * this->get_tracking_angle_degrees();
65
    // }
66

67
    void LinearFresnel::set_angles(double azimuth, double tilt)
12✔
68
    {
69
        if (azimuth < -180.0 || azimuth > 180.0)
12✔
70
        {
71
            std::stringstream ss;
2✔
72
            ss << "LinearFresnel::set_angles: Invalid azimuth angle ("
2✔
73
               << azimuth << "). Must be between -180 and 180 degrees.";
2✔
74
            throw std::invalid_argument(ss.str());
2✔
75
        }
2✔
76

77
        if (tilt < 0.0 || tilt > 90.0)
10✔
78
        {
79
            std::stringstream ss;
2✔
80
            ss << "LinearFresnel::set_angles: Invalid tilt angle ("
2✔
81
               << tilt << "). Must be between 0 and 90 degrees.";
2✔
82
            throw std::invalid_argument(ss.str());
2✔
83
        }
2✔
84

85
        this->coordinates_initialized = false;
8✔
86
        this->azimuth = azimuth;
8✔
87
        this->tilt = tilt;
8✔
88

89
        // Convert input angles to spherical coordinate angles
90
        double az = azimuth * D2R;
8✔
91
        double el = tilt * D2R;
8✔
92
        double pol = 0.5 * PI - az;
8✔
93
        double inc = 0.5 * PI - el;
8✔
94

95
        // Convert spherical coordinates to cartesian coordinates
96
        // y-axis
97
        this->rotation_axis.set_values(sin(inc) * cos(pol),
8✔
98
                                       sin(inc) * sin(pol),
8✔
99
                                       cos(inc));
100

101
        // z-axis
102
        this->neutral_normal.set_values(sin(-el) * cos(pol),
8✔
103
                                        sin(-el) * sin(pol),
8✔
104
                                        cos(-el));
105

106
        cross_product(this->rotation_axis,
8✔
107
                      this->neutral_normal,
8✔
108
                      this->tracking_origin);
8✔
109

110
        return;
8✔
111
    }
112

113
    void LinearFresnel::set_aperture_size(double len_x, double len_y)
15✔
114
    {
115
        if (len_x <= 0.0 || len_y <= 0.0)
15✔
116
        {
117
            std::stringstream ss;
5✔
118
            ss << "LinearFresnel::set_aperture_size: Invalid aperture dimensions. "
119
               << "len_x (" << len_x << ") and len_y (" << len_y
5✔
120
               << ") must be positive.";
5✔
121
            throw std::invalid_argument(ss.str());
5✔
122
        }
5✔
123

124
        this->initialized = false;
10✔
125
        this->aperture_size_x = len_x;
10✔
126
        this->aperture_size_y = len_y;
10✔
127

128
        return;
10✔
129
    }
130

131
    void LinearFresnel::set_focused_panels(bool focused)
5✔
132
    {
133
        this->initialized = false;
5✔
134
        this->focused_panels = focused;
5✔
135
        return;
5✔
136
    }
137

138
    void LinearFresnel::set_gaps(double gap_x,
10✔
139
                                 double gap_y,
140
                                 double gap_center)
141
    {
142
        if (gap_x < 0.0 || gap_y < 0.0 || gap_center < 0.0)
10✔
143
        {
144
            std::stringstream ss;
3✔
145
            ss << "LinearFresnel::set_gaps: Invalid gap dimensions. "
146
               << "gap_x (" << gap_x << "), gap_y (" << gap_y
3✔
147
               << "), and gap_center (" << gap_center
3✔
148
               << ") must be non-negative.";
3✔
149
            throw std::invalid_argument(ss.str());
3✔
150
        }
3✔
151

152
        this->initialized = false;
7✔
153
        this->gap_x = gap_x;
7✔
154
        this->gap_y = gap_y;
7✔
155
        this->gap_center = gap_center;
7✔
156

157
        return;
7✔
158
    }
159

160
    void LinearFresnel::set_number_panels(int_fast64_t num_x,
14✔
161
                                          int_fast64_t num_y)
162
    {
163
        if (num_x <= 0 || num_y <= 0)
14✔
164
        {
165
            std::stringstream ss;
4✔
166
            ss << "LinearFresnel::set_number_panels: Invalid panel count. "
167
               << "num_x (" << num_x << ") and num_y (" << num_y
4✔
168
               << ") must be positive.";
4✔
169
            throw std::invalid_argument(ss.str());
4✔
170
        }
4✔
171

172
        this->initialized = false;
10✔
173
        this->num_panels_x = num_x;
10✔
174
        this->num_panels_y = num_y;
10✔
175

176
        return;
10✔
177
    }
178

179
    void LinearFresnel::set_optics(const OpticalProperties &mirror,
4✔
180
                                   const OpticalProperties &absorber,
181
                                   const OpticalProperties &envelop_outer,
182
                                   const OpticalProperties &envelop_inner)
183
    {
184
        this->optics_mirror = mirror;
4✔
185
        this->optics_absorber = absorber;
4✔
186
        this->optics_env_out = envelop_outer;
4✔
187
        this->optics_env_in = envelop_inner;
4✔
188
        return;
4✔
189
    }
190

191
    void LinearFresnel::set_receiver_height(double height)
12✔
192
    {
193
        if (height <= 0.0)
12✔
194
        {
195
            std::stringstream ss;
2✔
196
            ss << "LinearFresnel::set_receiver_height: Invalid receiver height ("
2✔
197
               << height << "). Height must be positive.";
2✔
198
            throw std::invalid_argument(ss.str());
2✔
199
        }
2✔
200

201
        this->initialized = false;
10✔
202
        this->receiver_height = height;
10✔
203

204
        return;
10✔
205
    }
206

207
    void LinearFresnel::set_receiver_dimensions(double absorber_diameter,
17✔
208
                                                double envelop_diameter,
209
                                                double envelop_thickness)
210
    {
211
        if (absorber_diameter <= 0.0)
17✔
212
        {
213
            std::stringstream ss;
2✔
214
            ss << "LinearFresnel::set_receiver_dimensions: "
215
               << "Invalid absorber diameter (" << absorber_diameter
2✔
216
               << "). Must be positive.";
2✔
217
            throw std::invalid_argument(ss.str());
2✔
218
        }
2✔
219

220
        if (envelop_diameter <= 0.0)
15✔
221
        {
222
            std::stringstream ss;
2✔
223
            ss << "LinearFresnel::set_receiver_dimensions: "
224
               << "Invalid envelope diameter (" << envelop_diameter
2✔
225
               << "). Must be positive.";
2✔
226
            throw std::invalid_argument(ss.str());
2✔
227
        }
2✔
228

229
        if (envelop_thickness < 0.0)
13✔
230
        {
231
            std::stringstream ss;
1✔
232
            ss << "LinearFresnel::set_receiver_dimensions: "
233
               << "Invalid envelope thickness (" << envelop_thickness
1✔
234
               << "). Must be non-negative.";
1✔
235
            throw std::invalid_argument(ss.str());
1✔
236
        }
1✔
237

238
        if (absorber_diameter >= envelop_diameter)
12✔
239
        {
240
            std::stringstream ss;
2✔
241
            ss << "LinearFresnel::set_receiver_dimensions: "
242
               << "Absorber diameter (" << absorber_diameter
2✔
243
               << ") must be smaller than envelope diameter ("
2✔
244
               << envelop_diameter << ").";
2✔
245
            throw std::invalid_argument(ss.str());
2✔
246
        }
2✔
247

248
        this->initialized = false;
10✔
249
        this->abs_diameter = absorber_diameter;
10✔
250
        this->env_diameter = envelop_diameter;
10✔
251
        this->env_thickness = envelop_thickness;
10✔
252

253
        return;
10✔
254
    }
255

256
    void LinearFresnel::set_tracking_limits(double lower, double upper)
4✔
257
    {
258
        if (lower > upper)
4✔
259
        {
260
            std::stringstream ss;
1✔
261
            ss << "ParabolicTrough::set_tracking_limits: Invalid tracking "
262
               << "limits. Lower limit (" << lower
1✔
263
               << ") must be less than or equal to upper limit ("
1✔
264
               << upper << ").";
1✔
265
            throw std::invalid_argument(ss.str());
1✔
266
        }
1✔
267

268
        this->tracking_limit_lower = lower;
3✔
269
        this->tracking_limit_upper = upper;
3✔
270

271
        return;
3✔
272
    }
273

274
    void LinearFresnel::create_geometry()
11✔
275
    {
276
        if (this->initialized)
11✔
277
        {
NEW
278
            return;
×
279
        }
280

281
        // TODO: Does receiver height give the height to the center or bottom edge?
282
        // Currently assumes it is the center.
283

284
        this->enforce_user_fields_set();
11✔
285

286
        this->mirrors.clear();
7✔
287
        this->absorbers.clear();
7✔
288
        this->envelope.clear();
7✔
289
        this->clear();
7✔
290

291
        // Create mirrors
292
        if (this->num_panels_x == 1 && this->gap_center != 0.0)
7✔
293
        {
294
            // TODO: This should generate a warning.
NEW
295
            this->gap_center = 0.0;
×
NEW
296
            this->gap_x = 0.0;
×
297
        }
298

299
        double panel_len_x = this->aperture_size_x -
7✔
300
                             this->gap_x * (this->num_panels_x - 1);
7✔
301
        panel_len_x -= this->gap_center - this->gap_x;
7✔
302
        panel_len_x /= this->num_panels_x;
7✔
303
        double panel_x = -0.5 * this->aperture_size_x + 0.5 * panel_len_x;
7✔
304

305
        double panel_len_y = this->aperture_size_y -
7✔
306
                             this->gap_y * (this->num_panels_y - 1);
7✔
307
        panel_len_y /= this->num_panels_y;
7✔
308

309
        element_id sts;
310
        Vector3d origin;
7✔
311
        Vector3d aim;
7✔
312
        Vector3d receiver_pos(0.0,
313
                              0.0,
314
                              this->receiver_height);
7✔
315
        double panel_y, flen;
316
        single_element_ptr mirror;
7✔
317
        aperture_ptr ap;
7✔
318
        surface_ptr surf;
7✔
319
        Vector3d khat(0.0, 0.0, 1.0);
7✔
320

321
        for (int_fast64_t i = 0; i < this->num_panels_x; ++i)
31✔
322
        {
323
            panel_y = -0.5 * this->aperture_size_y + 0.5 * panel_len_y;
24✔
324
            for (int_fast64_t j = 0; j < this->num_panels_y; ++j)
89✔
325
            {
326
                mirror = make_element<SingleElement>();
65✔
327

328
                origin.set_values(panel_x, panel_y, 0.0);
65✔
329
                vector_add(1.0, receiver_pos, -1.0, origin, aim);
65✔
330
                // Project into xz-plane
331
                aim[1] = 0.0;
65✔
332
                make_unit_vector(aim);
65✔
333
                // std::cout << "Panel x: " << panel_x
334
                //           << "\nPanel y: " << panel_y
335
                //           << "\nRec: " << aim
336
                //           << std::endl;
337
                vector_add(0.5, khat, 0.5, aim);
65✔
338
                // std::cout << "Aim: " << aim << std::endl;
339
                vector_add(1.0, origin, 1000.0, aim);
65✔
340
                // std::cout << "Aim Point: " << aim << std::endl;
341
                // std::cout << "Origin: " << origin << std::endl;
342
                mirror->set_reference_frame_geometry(origin, aim, 0.0);
65✔
343

344
                ap = make_aperture<Rectangle>(panel_len_x, panel_len_y);
65✔
345
                mirror->set_aperture(ap);
65✔
346

347
                if (this->focused_panels)
65✔
348
                {
349
                    flen = sqrt(panel_x * panel_x +
48✔
350
                                this->receiver_height * this->receiver_height);
48✔
351
                    // surf = make_surface<Parabola>(flen,
352
                    //     std::numeric_limits<double>::infinity());
353
                    surf = make_surface<Parabola>(flen, 0.0);
48✔
354
                }
355
                else
356
                {
357
                    surf = make_surface<Flat>();
17✔
358
                }
359
                mirror->set_surface(surf);
65✔
360

361
                mirror->set_front_optical_properties(this->optics_mirror);
65✔
362
                mirror->set_back_optical_properties(this->optics_mirror);
65✔
363
                mirror->enable();
65✔
364

365
                std::stringstream name;
65✔
366
                name << "Mirror_" << this->num_panels_y * i + j;
65✔
367
                mirror->set_name(name.str());
65✔
368

369
                this->mirrors.push_back(mirror);
65✔
370
                sts = this->add_element(mirror);
65✔
371
                if (!Element::is_success(sts))
65✔
372
                {
373
                    // TODO: Make this more helpful
NEW
374
                    throw std::runtime_error("Failed to add mirror element");
×
375
                }
376

377
                panel_y += panel_len_y + this->gap_y;
65✔
378
            }
65✔
379
            panel_x += panel_len_x;
24✔
380
            if (i == this->num_panels_x / 2 - 1)
24✔
381
            {
382
                panel_x += this->gap_center;
6✔
383
            }
384
            else
385
            {
386
                panel_x += this->gap_x;
18✔
387
            }
388
        }
389

390
        // Absorber
391
        // TODO: Single element at the moment. Break up into multiple tubes.
392
        auto abs = make_element<SingleElement>();
7✔
393
        abs->set_name("Absorber");
14✔
394
        origin.set_values(0.0, 0.0,
7✔
395
                          this->receiver_height - 0.5 * this->abs_diameter);
7✔
396
        aim.set_values(0.0, 0.0, 1.0);
7✔
397
        vector_add(1.0, origin, 1.0, aim);
7✔
398
        abs->set_reference_frame_geometry(origin, aim, 0.0);
7✔
399
        abs->set_aperture(make_aperture<Rectangle>(this->abs_diameter,
14✔
400
                                                   this->aperture_size_y));
7✔
401
        abs->set_surface(make_surface<Cylinder>(0.5 * this->abs_diameter));
7✔
402
        abs->set_front_optical_properties(this->optics_absorber);
7✔
403
        abs->set_back_optical_properties(this->optics_absorber);
7✔
404
        abs->enable();
7✔
405

406
        this->absorbers.push_back(abs);
7✔
407
        sts = this->add_element(abs);
7✔
408
        if (!Element::is_success(sts))
7✔
409
        {
410
            // TODO: Make this more helpful
NEW
411
            throw std::runtime_error("Failed to add absorber element");
×
412
        }
413

414
        // Envelope -- Outer
415
        auto envout = make_element<SingleElement>();
7✔
416
        envout->set_name("EnvelopeOuter");
14✔
417
        origin.set_values(0.0, 0.0,
7✔
418
                          this->receiver_height - 0.5 * this->env_diameter);
7✔
419
        aim.set_values(0.0, 0.0, 1.0);
7✔
420
        vector_add(1.0, origin, 1.0, aim);
7✔
421
        envout->set_reference_frame_geometry(origin, aim, 0.0);
7✔
422
        envout->set_aperture(make_aperture<Rectangle>(this->env_diameter,
14✔
423
                                                      this->aperture_size_y));
7✔
424
        envout->set_surface(make_surface<Cylinder>(0.5 * this->env_diameter));
7✔
425
        envout->set_front_optical_properties(this->optics_env_out);
7✔
426
        envout->set_back_optical_properties(this->optics_env_out);
7✔
427
        envout->enable();
7✔
428

429
        this->envelope.push_back(envout);
7✔
430
        sts = this->add_element(envout);
7✔
431
        if (!Element::is_success(sts))
7✔
432
        {
NEW
433
            throw std::runtime_error("Failed to add outer envelope element");
×
434
        }
435

436
        // Envelope -- Inner
437
        auto envin = make_element<SingleElement>();
7✔
438
        envin->set_name("EnvelopeInner");
14✔
439
        origin.set_values(0.0, 0.0,
7✔
440
                          this->receiver_height -
7✔
441
                              0.5 * this->env_diameter + this->env_thickness);
7✔
442
        aim.set_values(0.0, 0.0, 1.0);
7✔
443
        vector_add(1.0, origin, 1.0, aim);
7✔
444
        envin->set_reference_frame_geometry(origin, aim, 0.0);
7✔
445
        double ap_x = this->env_diameter - 2 * this->env_thickness;
7✔
446
        double ap_y = this->aperture_size_y;
7✔
447
        envin->set_aperture(make_aperture<Rectangle>(ap_x, ap_y));
7✔
448
        envin->set_surface(make_surface<Cylinder>(0.5 * ap_x));
7✔
449
        envin->set_front_optical_properties(this->optics_env_in);
7✔
450
        envin->set_back_optical_properties(this->optics_env_in);
7✔
451
        envin->enable();
7✔
452
        this->envelope.push_back(envin);
7✔
453
        sts = this->add_element(envin);
7✔
454
        if (!Element::is_success(sts))
7✔
455
        {
NEW
456
            throw std::runtime_error("Failed to add inner envelope element");
×
457
        }
458

459
        this->initialized = true;
7✔
460

461
        return;
7✔
462
    }
7✔
463

464
    void LinearFresnel::update_geometry(double azimuth, double elevation)
8✔
465
    {
466
        if (elevation < 0.0 || elevation > 90.0)
8✔
467
        {
468
            std::stringstream ss;
2✔
469
            ss << "LinearFresnel::update_geometry: Invalid elevation ("
2✔
470
               << elevation << "). Elevation must lie between 0 and 90 degrees.";
2✔
471
            throw std::invalid_argument(ss.str());
2✔
472
        }
2✔
473

474
        if (azimuth < -180.0 || azimuth > 180.0)
6✔
475
        {
476
            std::stringstream ss;
2✔
477
            ss << "LinearFresnel::update_geometry: Invalid azimuth ("
2✔
478
               << elevation << "). Azimuth must lie between -180 and 180 degrees.";
2✔
479
            throw std::invalid_argument(ss.str());
2✔
480
        }
2✔
481

482
        if (!this->initialized)
4✔
483
        {
484
            std::stringstream ss;
1✔
485
            ss << "LinearFresnel::update_geometry: Uninitialized. "
486
               << "Call create_geometry() first.";
1✔
487
            throw std::invalid_argument(ss.str());
1✔
488
        }
1✔
489

490
        // NOTE: Setting the aim point and z-rotation only need to be done
491
        // once. But they need to be done after the LinearFresnel object
492
        // has been added to its reference element (if any) so we do it
493
        // here at the cost of repeating ourselves.
494

495
        // Set aim point
496
        Vector3d z_axis_ref, y_axis_ref;
3✔
497
        this->convert_global_to_reference(z_axis_ref, this->neutral_normal);
3✔
498
        this->convert_global_to_reference(y_axis_ref, this->rotation_axis);
3✔
499
        vector_add(1000.0, z_axis_ref, 1.0, this->origin, this->aim);
3✔
500

501
        // Set z-rotation
502
        double beta = asin(z_axis_ref[1]);
3✔
503
        double gamma = acos(y_axis_ref[1] / cos(beta));
3✔
504
        this->set_zrot_radians(gamma);
3✔
505

506
        // Update coordinate conversions so we can use them below
507
        this->coordinates_initialized = false;
3✔
508
        this->compute_coordinate_rotations();
3✔
509

510
        // std::cout << "Rotation axis: " << this->rotation_axis
511
        //           << "\nNeutral normal: " << this->neutral_normal
512
        //           << std::endl;
513

514
        // std::cout << "Global to Local: " << this->get_global_to_local()
515
        //           << std::endl;
516

517
        // std::cout << "z axis ref: " << z_axis_ref
518
        //           << "\ny axis ref: " << y_axis_ref
519
        //           << "\nBeta: " << beta
520
        //           << "\nGamma: " << gamma
521
        //           << std::endl;
522

523
        // Sun position projected into rotation plane and converted
524
        // to LinearFresnel object coordinates
525
        Vector3d sun_pos, sun_proj_local;
3✔
526
        sun_position_vector_degrees(sun_pos, azimuth, elevation);
3✔
527
        this->convert_vector_global_to_local(sun_proj_local, sun_pos);
3✔
528
        // Project to rotation plane
529
        sun_proj_local[1] = 0.0;
3✔
530
        sun_proj_local.make_unit();
3✔
531

532
        // std::cout << "Sun Position: " << sun_pos
533
        //           << "\nSun Proj Local: " << sun_proj_local
534
        //           << std::endl;
535

536
        // Set aimpoint for mirrors
537
        Vector3d aim_mirror_ref;
3✔
538
        // Absorber position projected into the plane of rotation (the xz-plane)
539
        Vector3d origin_abs_proj(0.0,
540
                                 0.0,
541
                                 this->receiver_height);
3✔
542
        // origin_abs_proj.make_unit();
543
        for (auto iter : this->mirrors)
11✔
544
        {
545
            // Get mirror to to receiver vector
546
            vector_add(1.0, origin_abs_proj,
8✔
547
                       -1.0, iter->get_origin_ref(),
16✔
548
                       aim_mirror_ref);
549

550
            // Project onto the rotation plane
551
            aim_mirror_ref[1] = 0.0;
8✔
552
            aim_mirror_ref.make_unit();
8✔
553
            // std::cout << "Origin: " << iter->get_origin_ref()
554
            //           << "\nMirror to Receiver: " << aim_mirror_ref
555
            //           << std::endl;
556

557
            // Take bisector vector with the sun
558
            vector_add(1.0, sun_proj_local, 1.0, aim_mirror_ref);
8✔
559
            aim_mirror_ref.make_unit();
8✔
560
            // std::cout << "Sun Proj Local: " << sun_proj_local
561
            //           << "\nAim Mirror Ref: " << aim_mirror_ref
562
            //           << std::endl;
563

564
            // Dot product with [0, 0, 1]
565
            double theta = acos(aim_mirror_ref[2]) * R2D;
8✔
566
            // Dot product with [1, 0, 0]
567
            if (aim_mirror_ref[0] < 0)
8✔
568
                theta = -theta;
4✔
569
            // std::cout << "Theta: " << theta << std::endl;
570

571
            if (theta < this->tracking_limit_lower)
8✔
572
            {
573
                theta = this->tracking_limit_lower * D2R;
2✔
574
                aim_mirror_ref.set_values(sin(theta), 0.0, cos(theta));
2✔
575
            }
576
            else if (theta > this->tracking_limit_upper)
6✔
577
            {
578
                theta = this->tracking_limit_upper * D2R;
2✔
579
                aim_mirror_ref.set_values(sin(theta), 0.0, cos(theta));
2✔
580
            }
581

582
            // std::cout << "Theta 2: " << theta * R2D
583
            //           << "\nAim Mirror Ref: " << aim_mirror_ref
584
            //           << std::endl;
585

586
            // Add origin of mirror
587
            vector_add(1.0, iter->get_origin_ref(), 1000.0, aim_mirror_ref);
8✔
588
            // aim_mirror_ref.scalar_mult(1000.0);
589

590
            // Set aim point
591
            iter->set_aim_vector(aim_mirror_ref);
8✔
592
            iter->compute_coordinate_rotations();
8✔
593
        }
8✔
594

595
        return;
6✔
596
    }
3✔
597

598
    void LinearFresnel::enforce_user_fields_set() const
13✔
599
    {
600
        if (this->initialized)
13✔
601
        {
602
            CompositeElement::enforce_user_fields_set();
2✔
603
        }
604

605
        // Validate that all required parameters have been set
606
        if (this->aperture_size_x <= 0.0 || this->aperture_size_y <= 0.0)
13✔
607
        {
608
            throw std::invalid_argument(
609
                "LinearFresnel: Aperture size must be set "
610
                "before creating geometry.");
1✔
611
        }
612

613
        if (this->num_panels_x <= 0 || this->num_panels_y <= 0)
12✔
614
        {
615
            throw std::invalid_argument(
616
                "LinearFresnel: Number of panels must be set "
617
                "before creating geometry.");
1✔
618
        }
619

620
        if (this->receiver_height <= 0.0)
11✔
621
        {
622
            throw std::invalid_argument(
623
                "LinearFresnel: Receiver height must be set "
624
                "before creating geometry.");
1✔
625
        }
626

627
        if (this->abs_diameter <= 0.0 ||
10✔
628
            this->env_diameter <= 0.0)
9✔
629
        {
630
            throw std::invalid_argument(
631
                "LinearFresnel: Receiver dimensions must be set "
632
                "before creating geometry.");
1✔
633
        }
634

635
        return;
9✔
636
    }
637

638
} // namespace SolTrace::Data
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