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

openmc-dev / openmc / 29167401541

11 Jul 2026 08:38PM UTC coverage: 80.762% (-0.5%) from 81.295%
29167401541

Pull #4010

github

web-flow
Merge 612540872 into e783e0147
Pull Request #4010: Implementation of surfaces with custom implicit function

18489 of 27369 branches covered (67.55%)

Branch coverage included in aggregate %.

1041 of 1330 new or added lines in 13 files covered. (78.27%)

76 existing lines in 3 files now uncovered.

60486 of 70418 relevant lines covered (85.9%)

53343422.58 hits per line

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

45.86
/src/implicit.cpp
1
#include "openmc/implicit.h"
2

3
#include <algorithm> // std::min, std::max
4
#include <cmath>
5
#include <limits>
6
#include <sstream>
7
#include <stdexcept>
8
#include <string>
9
#include <vector>
10

11
namespace openmc {
12

13
//==============================================================================
14
// Cache
15
//==============================================================================
16

17
thread_local StepCache step_cache;
2,147,483,647✔
18

19
//==============================================================================
20
// Implicit
21
//==============================================================================
22

23
double Implicit::along_ray(double t, Position r, Direction u) const
1,555,306,390✔
24
{
25
  return evaluate({r.x + t * u.x, r.y + t * u.y, r.z + t * u.z});
1,555,306,390✔
26
}
27

28
std::shared_ptr<Implicit> Implicit::from_xml_element(pugi::xml_node node,
3,900✔
29
  std::unordered_map<int, std::shared_ptr<Implicit>>& cache_map)
30
{
31
  std::string tag = node.name();
3,900✔
32

33
  // from_cache: look up a previously registered shared node.
34
  // Handled before child parsing - there are no children to recurse into.
35
  if (tag == "from_cache") {
3,900✔
36
    int id = node.attribute("id").as_int();
450✔
37
    auto it = cache_map.find(id);
450!
38
    if (it == cache_map.end())
450!
NEW
39
      throw std::runtime_error(
×
NEW
40
        "Implicit::from_xml_element: from_cache id=" + std::to_string(id) +
×
NEW
41
        " has no matching to_cache.");
×
42
    return it->second;
900!
43
  }
44

45
  // Recursively parse all element children.
46
  std::vector<std::shared_ptr<Implicit>> children;
3,450✔
47
  for (auto child : node.children()) {
7,230✔
48
    if (child.type() == pugi::node_element)
3,780!
49
      children.push_back(from_xml_element(child, cache_map));
7,560!
50
  }
51

52
  // to_cache: register the single child node by id, return a Cached wrapper.
53
  if (tag == "to_cache") {
3,450✔
54
    int id = node.attribute("id").as_int();
225✔
55
    auto cached = std::make_shared<implicit::Cached>(children[0]);
225✔
56
    cache_map[id] = cached;
225✔
57
    return cached;
225✔
58
  }
225✔
59

60
  // Dispatch table - mirrors Python's from_xml_element.
61
  if (tag == "x")
3,225✔
62
    return std::make_shared<implicit::X>();
120✔
63
  if (tag == "y")
3,105✔
64
    return std::make_shared<implicit::Y>();
120✔
65
  if (tag == "z")
2,985✔
66
    return std::make_shared<implicit::Z>();
180✔
67
  if (tag == "constant") {
2,805✔
68
    double v = node.attribute("value").as_double();
375✔
69
    return std::make_shared<implicit::Constant>(v);
375✔
70
  }
71
  if (tag == "add")
2,430✔
72
    return std::make_shared<implicit::Add>(children[0], children[1]);
405✔
73
  if (tag == "sub")
2,025✔
74
    return std::make_shared<implicit::Sub>(children[0], children[1]);
105✔
75
  if (tag == "mul")
1,920✔
76
    return std::make_shared<implicit::Mul>(children[0], children[1]);
300✔
77
  if (tag == "div")
1,620✔
78
    return std::make_shared<implicit::Div>(children[0], children[1]);
285✔
79
  if (tag == "scale") {
1,335✔
80
    double k = node.attribute("value").as_double();
495✔
81
    return std::make_shared<implicit::Scale>(children[0], k);
495✔
82
  }
83
  if (tag == "neg")
840!
NEW
84
    return std::make_shared<implicit::Neg>(children[0]);
×
85
  if (tag == "pow") {
840✔
86
    int exp = node.attribute("value").as_int();
330✔
87
    return std::make_shared<implicit::Pow>(children[0], exp);
330✔
88
  }
89
  if (tag == "sin")
510✔
90
    return std::make_shared<implicit::Sin>(children[0]);
75✔
91
  if (tag == "cos")
435✔
92
    return std::make_shared<implicit::Cos>(children[0]);
300✔
93
  if (tag == "sqrt")
135✔
94
    return std::make_shared<implicit::Sqrt>(children[0]);
60✔
95
  if (tag == "exp")
75!
NEW
96
    return std::make_shared<implicit::Exp>(children[0]);
×
97
  if (tag == "log")
75!
NEW
98
    return std::make_shared<implicit::Log>(children[0]);
×
99
  if (tag == "abs")
75✔
100
    return std::make_shared<implicit::Abs>(children[0]);
45✔
101
  if (tag == "max")
30!
102
    return std::make_shared<implicit::Max>(children[0], children[1]);
30✔
NEW
103
  if (tag == "min")
×
NEW
104
    return std::make_shared<implicit::Min>(children[0], children[1]);
×
105

NEW
106
  throw std::runtime_error(
×
NEW
107
    "Implicit::from_xml_element: unknown tag '" + tag + "'.");
×
108
}
7,350✔
109

110
std::shared_ptr<Implicit> Implicit::from_xml_element(pugi::xml_node node)
120✔
111
{
112
  std::unordered_map<int, std::shared_ptr<Implicit>> cache_map;
120✔
113
  return from_xml_element(node, cache_map);
120✔
114
}
120✔
115

116
std::string Implicit::to_xml_string() const
88✔
117
{
118
  pugi::xml_document doc;
88✔
119
  auto func_node = doc.append_child("function");
88✔
120
  std::unordered_map<const Implicit*, int> cache_map;
88✔
121
  to_xml_element(func_node, cache_map);
88✔
122
  std::ostringstream oss;
88✔
123
  doc.save(oss);
88✔
124
  return oss.str();
176✔
125
}
176✔
126

127
//==============================================================================
128
// Concrete node types  (openmc::implicit namespace)
129
//==============================================================================
130

131
namespace implicit {
132

133
//==============================================================================
134
// Helper functions for the product of two intervals [a,b] * [c,d]
135
//==============================================================================
136

137
static std::pair<double, double> interval_mul(
2,147,483,647✔
138
  double a, double b, double c, double d)
139
{
140
  double p1 = a * c, p2 = a * d, p3 = b * c, p4 = b * d;
2,147,483,647✔
141
  return {std::min({p1, p2, p3, p4}), std::max({p1, p2, p3, p4})};
2,147,483,647✔
142
}
143

144
//==============================================================================
145
// X
146
//==============================================================================
147

NEW
148
std::string X::expression() const
×
149
{
NEW
150
  return "X";
×
151
}
152
double X::evaluate(Position r) const
1,747,971,422✔
153
{
154
  return r.x;
1,747,971,422✔
155
}
156
Gradient X::gradient(Position r) const
20,004✔
157
{
158
  return {1.0, 0.0, 0.0};
20,004✔
159
}
160
double X::compute_lipschitz(Position r, Direction u, double t0, double t1) const
1,255,920,732✔
161
{
162
  return std::abs(u.x);
1,255,920,732✔
163
}
164
std::pair<double, double> X::compute_f_min_max(
2,147,483,647✔
165
  Position r, Direction u, double t0, double t1) const
166
{
167
  double f0 = r.x + t0 * u.x;
2,147,483,647✔
168
  double f1 = r.x + t1 * u.x;
2,147,483,647✔
169
  return {std::min(f0, f1), std::max(f0, f1)};
2,147,483,647✔
170
}
171
pugi::xml_node X::to_xml_element(pugi::xml_node parent,
88✔
172
  std::unordered_map<const Implicit*, int>& cache_map) const
173
{
174
  auto node = parent.append_child("x");
88✔
175
  return node;
88✔
176
}
177

178
//==============================================================================
179
// Y
180
//==============================================================================
181

NEW
182
std::string Y::expression() const
×
183
{
NEW
184
  return "Y";
×
185
}
186
double Y::evaluate(Position r) const
1,747,971,422✔
187
{
188
  return r.y;
1,747,971,422✔
189
}
190
Gradient Y::gradient(Position r) const
20,004✔
191
{
192
  return {0.0, 1.0, 0.0};
20,004✔
193
}
194
double Y::compute_lipschitz(Position r, Direction u, double t0, double t1) const
1,255,920,732✔
195
{
196
  return std::abs(u.y);
1,255,920,732✔
197
}
198
std::pair<double, double> Y::compute_f_min_max(
2,147,483,647✔
199
  Position r, Direction u, double t0, double t1) const
200
{
201
  double f0 = r.y + t0 * u.y;
2,147,483,647✔
202
  double f1 = r.y + t1 * u.y;
2,147,483,647✔
203
  return {std::min(f0, f1), std::max(f0, f1)};
2,147,483,647✔
204
}
205
pugi::xml_node Y::to_xml_element(pugi::xml_node parent,
88✔
206
  std::unordered_map<const Implicit*, int>& cache_map) const
207
{
208
  auto node = parent.append_child("y");
88✔
209
  return node;
88✔
210
}
211

212
//==============================================================================
213
// Z
214
//==============================================================================
215

NEW
216
std::string Z::expression() const
×
217
{
NEW
218
  return "Z";
×
219
}
220
double Z::evaluate(Position r) const
2,147,483,647✔
221
{
222
  return r.z;
2,147,483,647✔
223
}
224
Gradient Z::gradient(Position r) const
20,004✔
225
{
226
  return {0.0, 0.0, 1.0};
20,004✔
227
}
228
double Z::compute_lipschitz(Position r, Direction u, double t0, double t1) const
2,147,483,647✔
229
{
230
  return std::abs(u.z);
2,147,483,647✔
231
}
232
std::pair<double, double> Z::compute_f_min_max(
2,147,483,647✔
233
  Position r, Direction u, double t0, double t1) const
234
{
235
  double f0 = r.z + t0 * u.z;
2,147,483,647✔
236
  double f1 = r.z + t1 * u.z;
2,147,483,647✔
237
  return {std::min(f0, f1), std::max(f0, f1)};
2,147,483,647✔
238
}
239
pugi::xml_node Z::to_xml_element(pugi::xml_node parent,
132✔
240
  std::unordered_map<const Implicit*, int>& cache_map) const
241
{
242
  auto node = parent.append_child("z");
132✔
243
  return node;
132✔
244
}
245

246
//==============================================================================
247
// Constant
248
//==============================================================================
249

NEW
250
std::string Constant::expression() const
×
251
{
NEW
252
  return std::to_string(value_);
×
253
}
254
double Constant::evaluate(Position r) const
2,147,483,647✔
255
{
256
  return value_;
2,147,483,647✔
257
}
258
Gradient Constant::gradient(Position r) const
60,012✔
259
{
260
  return {0.0, 0.0, 0.0};
60,012✔
261
}
262
double Constant::compute_lipschitz(
2,147,483,647✔
263
  Position r, Direction u, double t0, double t1) const
264
{
265
  return 0.;
2,147,483,647✔
266
}
267
std::pair<double, double> Constant::compute_f_min_max(
2,147,483,647✔
268
  Position r, Direction u, double t0, double t1) const
269
{
270
  return {value_, value_};
2,147,483,647✔
271
}
272
pugi::xml_node Constant::to_xml_element(pugi::xml_node parent,
275✔
273
  std::unordered_map<const Implicit*, int>& cache_map) const
274
{
275
  auto node = parent.append_child("constant");
275✔
276
  node.append_attribute("value") = value_;
275✔
277
  return node;
275✔
278
}
279

280
//==============================================================================
281
// Add
282
//==============================================================================
283

NEW
284
std::string Add::expression() const
×
285
{
NEW
286
  return f_->expression() + " + " + g_->expression();
×
287
}
288
double Add::evaluate(Position r) const
2,147,483,647✔
289
{
290
  return f_->evaluate(r) + g_->evaluate(r);
2,147,483,647✔
291
}
292
Gradient Add::gradient(Position r) const
153,384✔
293
{
294
  return f_->gradient(r) + g_->gradient(r);
153,384✔
295
}
296
double Add::compute_lipschitz(
2,147,483,647✔
297
  Position r, Direction u, double t0, double t1) const
298
{
299
  return f_->compute_lipschitz(r, u, t0, t1) +
2,147,483,647✔
300
         g_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
301
}
302
std::pair<double, double> Add::compute_f_min_max(
2,147,483,647✔
303
  Position r, Direction u, double t0, double t1) const
304
{
305
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
306
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
307
  return {f_min + g_min, f_max + g_max};
2,147,483,647✔
308
}
309
pugi::xml_node Add::to_xml_element(pugi::xml_node parent,
297✔
310
  std::unordered_map<const Implicit*, int>& cache_map) const
311
{
312
  auto node = parent.append_child("add");
297✔
313
  f_->to_xml_element(node, cache_map);
297✔
314
  g_->to_xml_element(node, cache_map);
297✔
315
  return node;
297✔
316
}
317

318
//==============================================================================
319
// Sub
320
//==============================================================================
321

NEW
322
std::string Sub::expression() const
×
323
{
NEW
324
  return f_->expression() + " - " + g_->expression();
×
325
}
326
double Sub::evaluate(Position r) const
234,918,585✔
327
{
328
  return f_->evaluate(r) - g_->evaluate(r);
234,918,585✔
329
}
330
Gradient Sub::gradient(Position r) const
38,346✔
331
{
332
  return f_->gradient(r) - g_->gradient(r);
38,346✔
333
}
334
double Sub::compute_lipschitz(
187,562,573✔
335
  Position r, Direction u, double t0, double t1) const
336
{
337
  return f_->compute_lipschitz(r, u, t0, t1) +
187,562,573✔
338
         g_->compute_lipschitz(r, u, t0, t1);
187,562,573✔
339
}
340
std::pair<double, double> Sub::compute_f_min_max(
472,733,998✔
341
  Position r, Direction u, double t0, double t1) const
342
{
343
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
472,733,998✔
344
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
472,733,998✔
345
  return {f_min - g_max, f_max - g_min};
472,733,998✔
346
}
347
pugi::xml_node Sub::to_xml_element(pugi::xml_node parent,
77✔
348
  std::unordered_map<const Implicit*, int>& cache_map) const
349
{
350
  auto node = parent.append_child("sub");
77✔
351
  f_->to_xml_element(node, cache_map);
77✔
352
  g_->to_xml_element(node, cache_map);
77✔
353
  return node;
77✔
354
}
355

356
//==============================================================================
357
// Mul
358
//==============================================================================
359

NEW
360
std::string Mul::expression() const
×
361
{
NEW
362
  return f_->expression() + " * " + g_->expression();
×
363
}
364
double Mul::evaluate(Position r) const
2,147,483,647✔
365
{
366
  return f_->evaluate(r) * g_->evaluate(r);
2,147,483,647✔
367
}
368
Gradient Mul::gradient(Position r) const
191,730✔
369
{
370
  double fv = f_->evaluate(r);
191,730✔
371
  double gv = g_->evaluate(r);
191,730✔
372
  return gv * f_->gradient(r) + fv * g_->gradient(r);
191,730✔
373
}
374
double Mul::compute_lipschitz(
2,147,483,647✔
375
  Position r, Direction u, double t0, double t1) const
376
{
377
  double lipschitz_f = f_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
378
  double lipschitz_g = g_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
379
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
380
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
381
  double max_abs_f = std::max(std::abs(f_min), std::abs(f_max));
2,147,483,647✔
382
  double max_abs_g = std::max(std::abs(g_min), std::abs(g_max));
2,147,483,647✔
383
  return max_abs_f * lipschitz_g + max_abs_g * lipschitz_f;
2,147,483,647✔
384
}
385
std::pair<double, double> Mul::compute_f_min_max(
2,147,483,647✔
386
  Position r, Direction u, double t0, double t1) const
387
{
388
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
389
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
390
  auto [p_min, p_max] = interval_mul(f_min, f_max, g_min, g_max);
2,147,483,647✔
391
  return {p_min, p_max};
2,147,483,647✔
392
}
393
pugi::xml_node Mul::to_xml_element(pugi::xml_node parent,
220✔
394
  std::unordered_map<const Implicit*, int>& cache_map) const
395
{
396
  auto node = parent.append_child("mul");
220✔
397
  f_->to_xml_element(node, cache_map);
220✔
398
  g_->to_xml_element(node, cache_map);
220✔
399
  return node;
220✔
400
}
401

402
//==============================================================================
403
// Div
404
//==============================================================================
405

NEW
406
std::string Div::expression() const
×
407
{
NEW
408
  return f_->expression() + " / " + g_->expression();
×
409
}
410
double Div::evaluate(Position r) const
2,147,483,647✔
411
{
412
  double denom = g_->evaluate(r);
2,147,483,647✔
413
  if (denom == 0.0)
2,147,483,647!
NEW
414
    throw std::domain_error("Implicit Div: zero denominator at r=(" +
×
NEW
415
                            std::to_string(r.x) + ", " + std::to_string(r.y) +
×
NEW
416
                            ", " + std::to_string(r.z) + ") in expression " +
×
NEW
417
                            g_->expression());
×
418
  return f_->evaluate(r) / denom;
2,147,483,647✔
419
}
420
Gradient Div::gradient(Position r) const
60,012✔
421
{
422
  double fv = f_->evaluate(r);
60,012✔
423
  double gv = g_->evaluate(r);
60,012✔
424
  Gradient gf = f_->gradient(r);
60,012✔
425
  Gradient gg = g_->gradient(r);
60,012✔
426
  if (gv == 0.0) {
60,012!
NEW
427
    throw std::domain_error("Div::gradient: argument equals zero at r=(" +
×
NEW
428
                            std::to_string(r.x) + ", " + std::to_string(r.y) +
×
NEW
429
                            ", " + std::to_string(r.z) + ") in expression " +
×
NEW
430
                            g_->expression());
×
431
  }
432
  double inv_g2 = 1.0 / (gv * gv);
60,012✔
433
  return (gf * gv - fv * gg) * inv_g2;
60,012✔
434
}
435
double Div::compute_lipschitz(
2,147,483,647✔
436
  Position r, Direction u, double t0, double t1) const
437
{
438
  double lipschitz_f = f_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
439
  double lipschitz_g = g_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
440
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
441
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
2,147,483,647!
442
  double min_abs_g = std::min(std::abs(g_min), std::abs(g_max));
2,147,483,647!
443
  double max_abs_f = std::max(std::abs(f_min), std::abs(f_max));
2,147,483,647✔
444
  double max_abs_g = std::max(std::abs(g_min), std::abs(g_max));
2,147,483,647✔
445
  if (min_abs_g > 0.0) {
2,147,483,647!
446
    return (lipschitz_f * max_abs_g + max_abs_f * lipschitz_g) /
2,147,483,647✔
447
           (min_abs_g * min_abs_g);
2,147,483,647✔
448
  } else {
NEW
449
    throw std::domain_error(
×
NEW
450
      "Implicit Div: zero denominator on ray between r=(" +
×
NEW
451
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
452
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
453
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
454
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
455
      g_->expression());
×
456
  }
457
}
458
std::pair<double, double> Div::compute_f_min_max(
2,147,483,647✔
459
  Position r, Direction u, double t0, double t1) const
460
{
461
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
462
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
2,147,483,647!
463
  double inv_min, inv_max;
2,147,483,647✔
464
  if (g_min > 0.0) {
2,147,483,647!
465
    // g does not contain zero - compute 1/[g_min, g_max]
466
    inv_min = 1.0 / g_max;
2,147,483,647✔
467
    inv_max = 1.0 / g_min;
2,147,483,647✔
NEW
468
  } else if (g_max < 0.0) {
×
469
    // g does not contain zero - compute 1/[g_min, g_max]
NEW
470
    inv_min = 1.0 / g_min;
×
NEW
471
    inv_max = 1.0 / g_max;
×
472
  } else {
473
    // g straddles zero - not a lipschitz function.
NEW
474
    throw std::domain_error(
×
NEW
475
      "Implicit Div: zero denominator on ray between r=(" +
×
NEW
476
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
477
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
478
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
479
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
480
      g_->expression());
×
481
  }
482
  auto [p_min, p_max] = interval_mul(f_min, f_max, inv_min, inv_max);
2,147,483,647✔
483
  return {p_min, p_max};
2,147,483,647✔
484
}
485
pugi::xml_node Div::to_xml_element(pugi::xml_node parent,
209✔
486
  std::unordered_map<const Implicit*, int>& cache_map) const
487
{
488
  auto node = parent.append_child("div");
209✔
489
  f_->to_xml_element(node, cache_map);
209✔
490
  g_->to_xml_element(node, cache_map);
209✔
491
  return node;
209✔
492
}
493

494
//==============================================================================
495
// Scale
496
//==============================================================================
497

NEW
498
std::string Scale::expression() const
×
499
{
NEW
500
  return std::to_string(k_) + " * " + f_->expression();
×
501
}
502
double Scale::evaluate(Position r) const
2,147,483,647✔
503
{
504
  return k_ * f_->evaluate(r);
2,147,483,647✔
505
}
506
Gradient Scale::gradient(Position r) const
60,012✔
507
{
508
  return k_ * f_->gradient(r);
60,012✔
509
}
510
double Scale::compute_lipschitz(
2,147,483,647✔
511
  Position r, Direction u, double t0, double t1) const
512
{
513
  return std::abs(k_) * f_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
514
}
515
std::pair<double, double> Scale::compute_f_min_max(
2,147,483,647✔
516
  Position r, Direction u, double t0, double t1) const
517
{
518
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647!
519
  if (k_ >= 0.0) {
2,147,483,647!
520
    return {k_ * f_min, k_ * f_max};
2,147,483,647✔
521
  } else {
NEW
522
    return {k_ * f_max, k_ * f_min};
×
523
  }
524
}
525
pugi::xml_node Scale::to_xml_element(pugi::xml_node parent,
363✔
526
  std::unordered_map<const Implicit*, int>& cache_map) const
527
{
528
  auto node = parent.append_child("scale");
363✔
529
  node.append_attribute("value") = k_;
363✔
530
  f_->to_xml_element(node, cache_map);
363✔
531
  return node;
363✔
532
}
533

534
//==============================================================================
535
// Neg
536
//==============================================================================
537

NEW
538
std::string Neg::expression() const
×
539
{
NEW
540
  return "-" + f_->expression();
×
541
}
NEW
542
double Neg::evaluate(Position r) const
×
543
{
NEW
544
  return -f_->evaluate(r);
×
545
}
NEW
546
Gradient Neg::gradient(Position r) const
×
547
{
NEW
548
  return -f_->gradient(r);
×
549
}
NEW
550
double Neg::compute_lipschitz(
×
551
  Position r, Direction u, double t0, double t1) const
552
{
NEW
553
  return f_->compute_lipschitz(r, u, t0, t1);
×
554
}
NEW
555
std::pair<double, double> Neg::compute_f_min_max(
×
556
  Position r, Direction u, double t0, double t1) const
557
{
NEW
558
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
×
NEW
559
  return {-f_max, -f_min};
×
560
}
NEW
561
pugi::xml_node Neg::to_xml_element(pugi::xml_node parent,
×
562
  std::unordered_map<const Implicit*, int>& cache_map) const
563
{
NEW
564
  auto node = parent.append_child("neg");
×
NEW
565
  f_->to_xml_element(node, cache_map);
×
NEW
566
  return node;
×
567
}
568

569
//==============================================================================
570
// Pow
571
//==============================================================================
572

NEW
573
std::string Pow::expression() const
×
574
{
NEW
575
  return f_->expression() + " ** " + std::to_string(exp_);
×
576
}
577
double Pow::evaluate(Position r) const
2,147,483,647✔
578
{
579
  return std::pow(f_->evaluate(r), exp_);
2,147,483,647✔
580
}
NEW
581
Gradient Pow::gradient(Position r) const
×
582
{
583
  // Chain rule: (f^n)' = n * f^(n-1) * f'
NEW
584
  double fv = f_->evaluate(r);
×
NEW
585
  Gradient gf = f_->gradient(r);
×
NEW
586
  double coeff = exp_ * std::pow(fv, exp_ - 1.0);
×
NEW
587
  return coeff * gf;
×
588
}
589
double Pow::compute_lipschitz(
2,129,691,102✔
590
  Position r, Direction u, double t0, double t1) const
591
{
592
  double lipschitz_f = f_->compute_lipschitz(r, u, t0, t1);
2,129,691,102✔
593
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,129,691,102✔
594
  double max_abs_f = std::max(std::abs(f_min), std::abs(f_max));
2,129,691,102✔
595
  return std::abs(exp_) * std::pow(max_abs_f, exp_ - 1.0) * lipschitz_f;
2,129,691,102✔
596
}
597
std::pair<double, double> Pow::compute_f_min_max(
2,147,483,647✔
598
  Position r, Direction u, double t0, double t1) const
599
{
600
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
2,147,483,647!
601
  if (exp_ % 2 != 0) {
2,147,483,647!
602
    // Odd exponent: monotone increasing everywhere
NEW
603
    return {std::pow(f_min, exp_), std::pow(f_max, exp_)};
×
604
  } else {
605
    // Even exponent: minimum at 0 if interval straddles 0
606
    double p_min = std::pow(f_min, exp_);
2,147,483,647✔
607
    double p_max = std::pow(f_max, exp_);
2,147,483,647✔
608
    if (f_min <= 0.0 && f_max >= 0.0)
2,147,483,647✔
609
      return {0.0, std::max(p_min, p_max)};
83,708,746✔
610
    else
611
      return {std::min(p_min, p_max), std::max(p_min, p_max)};
2,147,483,647✔
612
  }
613
}
614
pugi::xml_node Pow::to_xml_element(pugi::xml_node parent,
242✔
615
  std::unordered_map<const Implicit*, int>& cache_map) const
616
{
617
  auto node = parent.append_child("pow");
242✔
618
  node.append_attribute("value") = exp_;
242✔
619
  f_->to_xml_element(node, cache_map);
242✔
620
  return node;
242✔
621
}
622

623
//==============================================================================
624
// Sin
625
//==============================================================================
626

NEW
627
std::string Sin::expression() const
×
628
{
NEW
629
  return "Sin(" + arg_->expression() + ")";
×
630
}
631
double Sin::evaluate(Position r) const
702,802,023✔
632
{
633
  return std::sin(arg_->evaluate(r));
702,802,023✔
634
}
635
Gradient Sin::gradient(Position r) const
191,730✔
636
{
637
  double c = std::cos(arg_->evaluate(r));
191,730✔
638
  Gradient g = arg_->gradient(r);
191,730✔
639
  return c * g;
191,730✔
640
}
641
double Sin::compute_lipschitz(
564,432,748✔
642
  Position r, Direction u, double t0, double t1) const
643
{
644
  return arg_->compute_lipschitz(r, u, t0, t1);
564,432,748✔
645
}
646
std::pair<double, double> Sin::compute_f_min_max(
1,128,865,496✔
647
  Position r, Direction u, double t0, double t1) const
648
{
649
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
1,128,865,496✔
650
  double sin_arg_min = std::sin(arg_min);
1,128,865,496✔
651
  double sin_arg_max = std::sin(arg_max);
1,128,865,496✔
652
  double f_min = std::min(sin_arg_min, sin_arg_max);
1,128,865,496✔
653
  double f_max = std::max(sin_arg_min, sin_arg_max);
1,128,865,496✔
654

655
  // sin reaches +1 at π/2 + 2kπ
656
  double period_arg_min = std::ceil((arg_min - M_PI / 2.0) / (2.0 * M_PI));
1,128,865,496✔
657
  double period_arg_max = std::floor((arg_max - M_PI / 2.0) / (2.0 * M_PI));
1,128,865,496✔
658
  if (period_arg_min <= period_arg_max)
1,128,865,496✔
659
    f_max = 1.0;
62,735,112✔
660

661
  // sin reaches -1 at -π/2 + 2kπ
662
  period_arg_min = std::ceil((arg_min + M_PI / 2.0) / (2.0 * M_PI));
1,128,865,496✔
663
  period_arg_max = std::floor((arg_max + M_PI / 2.0) / (2.0 * M_PI));
1,128,865,496✔
664
  if (period_arg_min <= period_arg_max)
1,128,865,496✔
665
    f_min = -1.0;
62,667,220✔
666

667
  return {f_min, f_max};
1,128,865,496✔
668
}
669
pugi::xml_node Sin::to_xml_element(pugi::xml_node parent,
55✔
670
  std::unordered_map<const Implicit*, int>& cache_map) const
671
{
672
  auto node = parent.append_child("sin");
55✔
673
  arg_->to_xml_element(node, cache_map);
55✔
674
  return node;
55✔
675
}
676

677
//==============================================================================
678
// Cos
679
//==============================================================================
680

NEW
681
std::string Cos::expression() const
×
682
{
NEW
683
  return "Cos(" + arg_->expression() + ")";
×
684
}
685
double Cos::evaluate(Position r) const
2,147,483,647✔
686
{
687
  return std::cos(arg_->evaluate(r));
2,147,483,647✔
688
}
689
Gradient Cos::gradient(Position r) const
191,730✔
690
{
691
  double c = -std::sin(arg_->evaluate(r));
191,730✔
692
  Gradient g = arg_->gradient(r);
191,730✔
693
  return c * g;
191,730✔
694
}
695
double Cos::compute_lipschitz(
2,147,483,647✔
696
  Position r, Direction u, double t0, double t1) const
697
{
698
  return arg_->compute_lipschitz(r, u, t0, t1);
2,147,483,647✔
699
}
700
std::pair<double, double> Cos::compute_f_min_max(
2,147,483,647✔
701
  Position r, Direction u, double t0, double t1) const
702
{
703
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
2,147,483,647✔
704
  double cos_arg_min = std::cos(arg_min);
2,147,483,647✔
705
  double cos_arg_max = std::cos(arg_max);
2,147,483,647✔
706
  double f_min = std::min(cos_arg_min, cos_arg_max);
2,147,483,647✔
707
  double f_max = std::max(cos_arg_min, cos_arg_max);
2,147,483,647✔
708

709
  // cos reaches +1 at 2kπ
710
  double period_arg_min = std::ceil(arg_min / (2.0 * M_PI));
2,147,483,647✔
711
  double period_arg_max = std::floor(arg_max / (2.0 * M_PI));
2,147,483,647✔
712
  if (period_arg_min <= period_arg_max)
2,147,483,647✔
713
    f_max = 1.0;
424,668,134✔
714

715
  // cos reaches -1 at π + 2kπ
716
  period_arg_min = std::ceil((arg_min - M_PI) / (2.0 * M_PI));
2,147,483,647✔
717
  period_arg_max = std::floor((arg_max - M_PI) / (2.0 * M_PI));
2,147,483,647✔
718
  if (period_arg_min <= period_arg_max)
2,147,483,647✔
719
    f_min = -1.0;
424,757,586✔
720

721
  return {f_min, f_max};
2,147,483,647✔
722
}
723
pugi::xml_node Cos::to_xml_element(pugi::xml_node parent,
220✔
724
  std::unordered_map<const Implicit*, int>& cache_map) const
725
{
726
  auto node = parent.append_child("cos");
220✔
727
  arg_->to_xml_element(node, cache_map);
220✔
728
  return node;
220✔
729
}
730

731
//==============================================================================
732
// Sqrt
733
//==============================================================================
734

NEW
735
std::string Sqrt::expression() const
×
736
{
NEW
737
  return "Sqrt(" + arg_->expression() + ")";
×
738
}
739
double Sqrt::evaluate(Position r) const
144,001,027✔
740
{
741
  return std::sqrt(arg_->evaluate(r));
144,001,027✔
742
}
NEW
743
Gradient Sqrt::gradient(Position r) const
×
744
{
NEW
745
  double argv = arg_->evaluate(r);
×
NEW
746
  if (argv <= 0.0) {
×
NEW
747
    throw std::domain_error(
×
NEW
748
      "Sqrt::gradient: argument equals zero or negative r=(" +
×
NEW
749
      std::to_string(r.x) + ", " + std::to_string(r.y) + ", " +
×
NEW
750
      std::to_string(r.z) + ") in expression " + arg_->expression());
×
751
  }
NEW
752
  double c = 0.5 / std::sqrt(argv);
×
NEW
753
  Gradient g = arg_->gradient(r);
×
NEW
754
  return c * g;
×
755
}
756
double Sqrt::compute_lipschitz(
96,252,926✔
757
  Position r, Direction u, double t0, double t1) const
758
{
759
  // L(√f) = L(f) / (2 * √f_min) - derivative 1/(2√f) is largest at f_min
760
  double lipschitz_arg = arg_->compute_lipschitz(r, u, t0, t1);
96,252,926✔
761
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
96,252,926!
762
  if (arg_min <= 0.0)
96,252,926!
NEW
763
    throw std::domain_error(
×
764
      "Sqrt::compute_lipschitz: argument reaches zero or negative on ray "
NEW
765
      "between r=(" +
×
NEW
766
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
767
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
768
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
769
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
770
      arg_->expression());
×
771
  return lipschitz_arg / (2.0 * std::sqrt(arg_min));
96,252,926✔
772
}
773
std::pair<double, double> Sqrt::compute_f_min_max(
145,057,352✔
774
  Position r, Direction u, double t0, double t1) const
775
{
776
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
145,057,352!
777
  if (arg_min < 0.0)
145,057,352!
NEW
778
    throw std::domain_error(
×
NEW
779
      "Sqrt::compute_f_min_max: argument is negative on ray between r=(" +
×
NEW
780
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
781
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
782
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
783
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
784
      arg_->expression());
×
785
  return {std::sqrt(arg_min), std::sqrt(arg_max)};
145,057,352✔
786
}
787
pugi::xml_node Sqrt::to_xml_element(pugi::xml_node parent,
44✔
788
  std::unordered_map<const Implicit*, int>& cache_map) const
789
{
790
  auto node = parent.append_child("sqrt");
44✔
791
  arg_->to_xml_element(node, cache_map);
44✔
792
  return node;
44✔
793
}
794

795
//==============================================================================
796
// Exp
797
//==============================================================================
798

NEW
799
std::string Exp::expression() const
×
800
{
NEW
801
  return "Exp(" + arg_->expression() + ")";
×
802
}
NEW
803
double Exp::evaluate(Position r) const
×
804
{
NEW
805
  return std::exp(arg_->evaluate(r));
×
806
}
NEW
807
Gradient Exp::gradient(Position r) const
×
808
{
NEW
809
  double c = std::exp(arg_->evaluate(r));
×
NEW
810
  Gradient g = arg_->gradient(r);
×
NEW
811
  return c * g;
×
812
}
NEW
813
double Exp::compute_lipschitz(
×
814
  Position r, Direction u, double t0, double t1) const
815
{
NEW
816
  double lipschitz_arg = arg_->compute_lipschitz(r, u, t0, t1);
×
NEW
817
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
×
NEW
818
  return lipschitz_arg * std::exp(arg_max);
×
819
}
NEW
820
std::pair<double, double> Exp::compute_f_min_max(
×
821
  Position r, Direction u, double t0, double t1) const
822
{
NEW
823
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
×
NEW
824
  return {std::exp(arg_min), std::exp(arg_max)};
×
825
}
NEW
826
pugi::xml_node Exp::to_xml_element(pugi::xml_node parent,
×
827
  std::unordered_map<const Implicit*, int>& cache_map) const
828
{
NEW
829
  auto node = parent.append_child("exp");
×
NEW
830
  arg_->to_xml_element(node, cache_map);
×
NEW
831
  return node;
×
832
}
833

834
//==============================================================================
835
// Log
836
//==============================================================================
837

NEW
838
std::string Log::expression() const
×
839
{
NEW
840
  return "Log(" + arg_->expression() + ")";
×
841
}
NEW
842
double Log::evaluate(Position r) const
×
843
{
NEW
844
  return std::log(arg_->evaluate(r));
×
845
}
NEW
846
Gradient Log::gradient(Position r) const
×
847
{
NEW
848
  double argv = arg_->evaluate(r);
×
NEW
849
  if (argv <= 0.0) {
×
NEW
850
    throw std::domain_error(
×
NEW
851
      "Log::gradient: argument equals zero or negative r=(" +
×
NEW
852
      std::to_string(r.x) + ", " + std::to_string(r.y) + ", " +
×
NEW
853
      std::to_string(r.z) + ") in expression " + arg_->expression());
×
854
  }
NEW
855
  double c = 1 / argv;
×
NEW
856
  Gradient g = arg_->gradient(r);
×
NEW
857
  return c * g;
×
858
}
NEW
859
double Log::compute_lipschitz(
×
860
  Position r, Direction u, double t0, double t1) const
861
{
NEW
862
  double lipschitz_arg = arg_->compute_lipschitz(r, u, t0, t1);
×
NEW
863
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
×
NEW
864
  if (arg_min <= 0.0)
×
NEW
865
    throw std::domain_error(
×
866
      "Log::compute_lipschitz: argument reaches zero or negative on ray "
NEW
867
      "between r=(" +
×
NEW
868
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
869
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
870
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
871
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
872
      arg_->expression());
×
NEW
873
  return lipschitz_arg / arg_min;
×
874
}
NEW
875
std::pair<double, double> Log::compute_f_min_max(
×
876
  Position r, Direction u, double t0, double t1) const
877
{
NEW
878
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
×
NEW
879
  if (arg_min <= 0.0)
×
NEW
880
    throw std::domain_error(
×
881
      "Log::compute_f_min_max: argument is zero or negative on ray between "
NEW
882
      "r=(" +
×
NEW
883
      std::to_string(r.x + t0 * u.x) + ", " + std::to_string(r.y + t0 * u.y) +
×
NEW
884
      ", " + std::to_string(r.z + t0 * u.z) + ") and r=(" +
×
NEW
885
      std::to_string(r.x + t1 * u.x) + ", " + std::to_string(r.y + t1 * u.y) +
×
NEW
886
      ", " + std::to_string(r.z + t1 * u.z) + ") in expression " +
×
NEW
887
      arg_->expression());
×
NEW
888
  return {std::log(arg_min), std::log(arg_max)};
×
889
}
NEW
890
pugi::xml_node Log::to_xml_element(pugi::xml_node parent,
×
891
  std::unordered_map<const Implicit*, int>& cache_map) const
892
{
NEW
893
  auto node = parent.append_child("log");
×
NEW
894
  arg_->to_xml_element(node, cache_map);
×
NEW
895
  return node;
×
896
}
897

898
//==============================================================================
899
// Abs
900
//==============================================================================
901

NEW
902
std::string Abs::expression() const
×
903
{
NEW
904
  return "|" + arg_->expression() + "|";
×
905
}
906
double Abs::evaluate(Position r) const
246,873,576✔
907
{
908
  return std::abs(arg_->evaluate(r));
246,873,576✔
909
}
NEW
910
Gradient Abs::gradient(Position r) const
×
911
{
NEW
912
  double c = std::copysign(1.0, arg_->evaluate(r));
×
NEW
913
  Gradient g = arg_->gradient(r);
×
NEW
914
  return c * g;
×
915
}
916
double Abs::compute_lipschitz(
142,345,500✔
917
  Position r, Direction u, double t0, double t1) const
918
{
919
  return arg_->compute_lipschitz(r, u, t0, t1);
142,345,500✔
920
}
921
std::pair<double, double> Abs::compute_f_min_max(
142,345,500✔
922
  Position r, Direction u, double t0, double t1) const
923
{
924
  auto [arg_min, arg_max] = arg_->compute_f_min_max(r, u, t0, t1);
142,345,500✔
925
  if (arg_min >= 0.0)
142,345,500✔
926
    return {arg_min, arg_max}; // entirely non-negative
68,630,892✔
927
  else if (arg_max <= 0.0)
73,714,608✔
928
    return {-arg_max, -arg_min}; // entirely non-positive
71,765,606✔
929
  else
930
    return {0.0, std::max(-arg_min, arg_max)}; // straddles zero
2,919,323✔
931
}
932
pugi::xml_node Abs::to_xml_element(pugi::xml_node parent,
33✔
933
  std::unordered_map<const Implicit*, int>& cache_map) const
934
{
935
  auto node = parent.append_child("abs");
33✔
936
  arg_->to_xml_element(node, cache_map);
33✔
937
  return node;
33✔
938
}
939

940
//==============================================================================
941
// Min
942
//==============================================================================
943

NEW
944
std::string Min::expression() const
×
945
{
NEW
946
  return "Min(" + f_->expression() + ", " + g_->expression() + ")";
×
947
}
NEW
948
double Min::evaluate(Position r) const
×
949
{
NEW
950
  return std::min(f_->evaluate(r), g_->evaluate(r));
×
951
}
NEW
952
Gradient Min::gradient(Position r) const
×
953
{
NEW
954
  return (f_->evaluate(r) <= g_->evaluate(r)) ? f_->gradient(r)
×
NEW
955
                                              : g_->gradient(r);
×
956
}
NEW
957
double Min::compute_lipschitz(
×
958
  Position r, Direction u, double t0, double t1) const
959
{
960
  // L(min(f,g)) = max(L_f, L_g)  - same argument as Max by symmetry.
NEW
961
  return std::max(
×
NEW
962
    f_->compute_lipschitz(r, u, t0, t1), g_->compute_lipschitz(r, u, t0, t1));
×
963
}
NEW
964
std::pair<double, double> Min::compute_f_min_max(
×
965
  Position r, Direction u, double t0, double t1) const
966
{
NEW
967
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
×
NEW
968
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
×
NEW
969
  return {std::min(f_min, g_min), std::min(f_max, g_max)};
×
970
}
NEW
971
pugi::xml_node Min::to_xml_element(pugi::xml_node parent,
×
972
  std::unordered_map<const Implicit*, int>& cache_map) const
973
{
NEW
974
  auto node = parent.append_child("min");
×
NEW
975
  f_->to_xml_element(node, cache_map);
×
NEW
976
  g_->to_xml_element(node, cache_map);
×
NEW
977
  return node;
×
978
}
979

980
//==============================================================================
981
// Max
982
//==============================================================================
983

NEW
984
std::string Max::expression() const
×
985
{
NEW
986
  return "Max(" + f_->expression() + ", " + g_->expression() + ")";
×
987
}
988
double Max::evaluate(Position r) const
164,582,384✔
989
{
990
  return std::max(f_->evaluate(r), g_->evaluate(r));
164,582,384✔
991
}
NEW
992
Gradient Max::gradient(Position r) const
×
993
{
NEW
994
  return (f_->evaluate(r) >= g_->evaluate(r)) ? f_->gradient(r)
×
NEW
995
                                              : g_->gradient(r);
×
996
}
997
double Max::compute_lipschitz(
94,897,000✔
998
  Position r, Direction u, double t0, double t1) const
999
{
1000
  return std::max(
189,794,000✔
1001
    f_->compute_lipschitz(r, u, t0, t1), g_->compute_lipschitz(r, u, t0, t1));
94,897,000✔
1002
}
1003
std::pair<double, double> Max::compute_f_min_max(
94,897,000✔
1004
  Position r, Direction u, double t0, double t1) const
1005
{
1006
  auto [f_min, f_max] = f_->compute_f_min_max(r, u, t0, t1);
94,897,000✔
1007
  auto [g_min, g_max] = g_->compute_f_min_max(r, u, t0, t1);
94,897,000✔
1008
  return {std::max(f_min, g_min), std::max(f_max, g_max)};
134,384,954✔
1009
}
1010
pugi::xml_node Max::to_xml_element(pugi::xml_node parent,
22✔
1011
  std::unordered_map<const Implicit*, int>& cache_map) const
1012
{
1013
  auto node = parent.append_child("max");
22✔
1014
  f_->to_xml_element(node, cache_map);
22✔
1015
  g_->to_xml_element(node, cache_map);
22✔
1016
  return node;
22✔
1017
}
1018

1019
//==============================================================================
1020
// Cached
1021
//==============================================================================
1022

1023
std::atomic<int> Cached::next_slot_ {0};
1024

1025
CacheEntry& Cached::get_cache_entry() const
2,147,483,647✔
1026
{
1027
  auto& cache = step_cache.node_cache;
2,147,483,647✔
1028
  if (static_cast<size_t>(slot_) >= cache.size()) {
2,147,483,647✔
1029
    // First access on this thread - grow to cover all slots assigned so far.
1030
    cache.resize(next_slot_.load());
120✔
1031
  }
1032
  return cache[slot_];
2,147,483,647✔
1033
}
1034

1035
CacheEntry& Cached::refresh(Position r) const
2,147,483,647✔
1036
{
1037
  CacheEntry& entry = get_cache_entry();
2,147,483,647✔
1038
  bool pos_match = entry.r.x == r.x && entry.r.y == r.y && entry.r.z == r.z;
2,147,483,647!
1039
  bool epoch_match = entry.epoch == step_cache.epoch;
2,147,483,647✔
1040
  if (!epoch_match || !pos_match) {
2,147,483,647✔
1041
    entry.eval = child_->evaluate(r);
2,147,483,647✔
1042
    entry.grad_valid = false;
2,147,483,647✔
1043
    entry.L_valid = false;
2,147,483,647✔
1044
    entry.min_max_valid = false;
2,147,483,647✔
1045
    entry.epoch = step_cache.epoch;
2,147,483,647✔
1046
    entry.r = r;
2,147,483,647✔
1047
  }
1048
  return entry;
2,147,483,647✔
1049
}
1050

1051
CacheEntry& Cached::refresh_interval(
2,147,483,647✔
1052
  Position r, Direction u, double t0, double t1) const
1053
{
1054
  CacheEntry& entry = refresh(r);
2,147,483,647✔
1055
  bool dir_match = entry.u.x == u.x && entry.u.y == u.y && entry.u.z == u.z;
2,147,483,647!
1056
  bool interval_match = entry.t0 == t0 && entry.t1 == t1;
2,147,483,647✔
1057
  if (!dir_match || !interval_match) {
2,147,483,647✔
1058
    entry.L_valid = false;
1,381,207,500✔
1059
    entry.min_max_valid = false;
1,381,207,500✔
1060
    entry.u = u;
1,381,207,500✔
1061
    entry.t0 = t0;
1,381,207,500✔
1062
    entry.t1 = t1;
1,381,207,500✔
1063
  }
1064
  return entry;
2,147,483,647✔
1065
}
1066

NEW
1067
std::string Cached::expression() const
×
1068
{
NEW
1069
  return "@[" + child_->expression() + "]";
×
1070
}
1071

1072
double Cached::evaluate(Position r) const
2,147,483,647✔
1073
{
1074
  return refresh(r).eval;
2,147,483,647✔
1075
}
1076

1077
Gradient Cached::gradient(Position r) const
460,152✔
1078
{
1079
  CacheEntry& entry = refresh(r);
460,152✔
1080
  if (!entry.grad_valid) {
460,152✔
1081
    entry.grad = child_->gradient(r);
60,012✔
1082
    entry.grad_valid = true;
60,012✔
1083
  }
1084
  return entry.grad;
460,152✔
1085
}
1086

1087
double Cached::compute_lipschitz(
2,147,483,647✔
1088
  Position r, Direction u, double t0, double t1) const
1089
{
1090
  CacheEntry& entry = refresh_interval(r, u, t0, t1);
2,147,483,647✔
1091
  if (!entry.L_valid) {
2,147,483,647✔
1092
    entry.L = child_->compute_lipschitz(r, u, t0, t1);
1,381,211,227✔
1093
    entry.L_valid = true;
1,381,211,227✔
1094
  }
1095
  return entry.L;
2,147,483,647✔
1096
}
1097

1098
std::pair<double, double> Cached::compute_f_min_max(
2,147,483,647✔
1099
  Position r, Direction u, double t0, double t1) const
1100
{
1101
  CacheEntry& entry = refresh_interval(r, u, t0, t1);
2,147,483,647✔
1102
  if (!entry.min_max_valid) {
2,147,483,647✔
1103
    entry.min_max = child_->compute_f_min_max(r, u, t0, t1);
1,381,211,227✔
1104
    entry.min_max_valid = true;
1,381,211,227✔
1105
  }
1106
  return entry.min_max;
2,147,483,647✔
1107
}
1108

1109
pugi::xml_node Cached::to_xml_element(pugi::xml_node parent,
495✔
1110
  std::unordered_map<const Implicit*, int>& cache_map) const
1111
{
1112
  auto it = cache_map.find(this);
495✔
1113
  if (it != cache_map.end()) {
495✔
1114
    // Already emitted - write a back-reference
1115
    auto node = parent.append_child("from_cache");
330✔
1116
    node.append_attribute("id") = it->second;
330✔
1117
    return node;
330✔
1118
  }
1119
  // First visit - register and emit the full subtree
1120
  int id = static_cast<int>(cache_map.size());
165✔
1121
  cache_map[this] = id;
165✔
1122
  auto node = parent.append_child("to_cache");
165✔
1123
  node.append_attribute("id") = id;
165✔
1124
  child_->to_xml_element(node, cache_map);
165✔
1125
  return node;
165✔
1126
}
1127

1128
} // namespace implicit
1129
} // namespace openmc
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