Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Triply Periodic Minimal Surfaces (TPMS) in OpenMC #3292

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
194c53a
First implementation of TPMS - choices of implementation not definiti…
Oct 9, 2024
cad5288
More readable README and updated gitignore to remove .ppm extension
Oct 9, 2024
34dfcbc
Tested instructions of README for a smooth installation
Oct 9, 2024
aba087b
moved TPMS test, shebang removed and README typo
Oct 10, 2024
4b64056
Add MC2025 example
Oct 16, 2024
d135cc9
tpms ray tracing bug + memory leak fix + p_schwartz_pwr non-regression
Oct 30, 2024
ff3c463
hard code bisect function from boost
Oct 31, 2024
c01af03
user friendly tpms classmethod
Oct 31, 2024
5456834
More efficient code for ray tracing and some typos
Oct 31, 2024
d737e1a
Templating TPMS for easier implementation in the future
Nov 4, 2024
1baeb79
revert previous templating, not efficient (div by 3 the perfs)
Nov 4, 2024
0e89a7a
Cleaner implementation, removing equations in the surface files to pu…
Nov 6, 2024
e3a9932
Corrected minor bug (root close to zero) + implementation of Diamond …
Nov 20, 2024
887929a
***Last commit before fTPMS implementation. Improved raytracing algor…
Nov 21, 2024
5e110f2
***MAJOR IMPLEMENTATION: function TPMS
Dec 4, 2024
d68db81
add constant pitch and constant thickness test
Dec 4, 2024
c7c1d02
add volume calc in interpolated tests
Dec 4, 2024
dd4d98f
*Final commit before merge. Updated regression tests. Improved ray-tr…
Feb 5, 2025
fe2018b
Merge branch 'TPMS' into develop
Feb 5, 2025
c730d3a
Formatting + Docstrings
Feb 5, 2025
23f626b
*Post review commit of @gridley. Adopted xml-to-xtensor logic. Remove…
Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ CMakeSettings.json

# Python pickle files
*.pkl

# Validation
validation
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vendor/pugixml/CMakeLists.txt")
turned off or failed. Please update submodules and try again.")
endif()

#===============================================================================
# boost library
#===============================================================================

find_package(Boost 1.85.0 REQUIRED)

#===============================================================================
# pugixml library
#===============================================================================
Expand Down Expand Up @@ -430,6 +436,8 @@ list(APPEND libopenmc_SOURCES
src/tallies/trigger.cpp
src/thermal.cpp
src/timer.cpp
src/tpms.cpp
src/tpms_functions.cpp
src/track_output.cpp
src/universe.cpp
src/urr.cpp
Expand Down
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
# TPMS branch specific (INL developers)

<img src="https://media1.tenor.com/m/98dCgWDs4rsAAAAC/epic-sax-guy-sax.gif" width="100px" align="center">

## Installation

##### create a new conda env:
```
conda create -n openmc-TPMS moose-dev=2024.10.01=mpich
conda deactivate
conda activate openmc-TPMS
# conda install boost=1.85.0 # not needed anymore
```
##### clone the project to your local repo:
```
git clone [email protected]:paul-ferney/openmc.git
cd openmc
git checkout TPMS
```
##### install openmc python API in dev mode:
```
python -m pip install -e .[test]
```
##### check that your compilers are from your conda env:
```
which $CC && which $CXX
>>> /opt/anaconda3/envs/openmc-TPMS/bin/mpicc
>>> /opt/anaconda3/envs/openmc-TPMS/bin/mpicxx
```
##### create a build directory and make your project:
```
mkdir ./build && cd ./build
cmake .. && make -j 12
cd ..
```
##### test openmc:
```
cd tests/regression_tests/TPMS/base_no_tpms
python base_no_tpms.py
../../../build/bin/openmc
```

## Trouble shooting
### Shared libraries conflict
When running cmake, you might get:
Warning Cannot generate a safe runtime search path for target libopenmc because files in some directories may conflict with libraries in implicit directories:
```
CMake Warning at CMakeLists.txt:463 (add_library):
Cannot generate a safe runtime search path for target libopenmc because
files in some directories may conflict with libraries in implicit
directories:

runtime library [libpng.dylib] in /opt/anaconda3/envs/openmc-TPMS/lib may be hidden by files in:
/opt/homebrew/lib
runtime library [libpugixml.1.dylib] in /opt/anaconda3/envs/openmc-TPMS/lib may be hidden by files in:
/opt/homebrew/lib

Some of these libraries may not be found correctly.
```
There is a conflict between your conda env shared libraries and shared libraries installed somewhere else. You want cmake to strictly use your conda libs. Run instead:
```
cmake .. -DCMAKE_PREFIX_PATH=/opt/anaconda3/envs/openmc-TPMS/ -DOPENMC_USE_MPI=ON -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=FALSE -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=FALSE
```

# OpenMC Monte Carlo Particle Transport Code

[![License](https://img.shields.io/badge/license-MIT-green)](https://docs.openmc.org/en/latest/license.html)
Expand Down
91 changes: 91 additions & 0 deletions include/openmc/bisect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// taken fom boost1.85 - boost::math::tools::bisect
//
// Boost Software License - Version 1.0 - August 17th, 2003

// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef OPENMC_BISECT_H
#define OPENMC_BISECT_H

#include <iostream>

template<class F, class Tol>
std::pair<double, double> bisect(
F f, double min, double max, Tol tol, std::uintmax_t& max_iter)
{
double fmin = f(min);
double fmax = f(max);
if (fmin == 0) {
max_iter = 2;
return std::make_pair(min, min);
}
if (fmax == 0) {
max_iter = 2;
return std::make_pair(max, max);
}

//
// Error checking:
//
if (min >= max) {
throw std::invalid_argument("Arguments min and max in wrong order");
}
if (fmin * fmax >= 0) {
throw std::invalid_argument(
"No change of sign, either there is no root to find, or there are "
"multiple roots in the interval");
}

//
// Three function invocations so far:
//
std::uintmax_t count = max_iter;
if (count < 3) {
count = 0;
} else {
count -= 3;
}

while (count && (0 == tol(min, max))) {
double mid = (min + max) / 2;
double fmid = f(mid);
if ((mid == max) || (mid == min))
break;
if (fmid == 0) {
min = max = mid;
break;
} else if (std::signbit(fmid) != std::signbit(fmin)) {
max = mid;
} else {
min = mid;
fmin = fmid;
}
--count;
}

max_iter -= count;

return std::make_pair(min, max);
}

#endif
2 changes: 2 additions & 0 deletions include/openmc/cell.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Region {

//! Get Boolean of if the cell is simple or not
bool is_simple() const { return simple_; }
int32_t cell_id() const { return cell_id_; }

private:
//----------------------------------------------------------------------------
Expand Down Expand Up @@ -144,6 +145,7 @@ class Region {
// TODO: Should this be a vector of some other type
vector<int32_t> expression_;
bool simple_; //!< Does the region contain only intersections?
int32_t cell_id_;
};

//==============================================================================
Expand Down
64 changes: 63 additions & 1 deletion include/openmc/surface.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define OPENMC_SURFACE_H

#include <limits> // For numeric_limits
#include <list>
#include <string>
#include <unordered_map>

Expand All @@ -16,6 +17,9 @@
#include "openmc/position.h"
#include "openmc/vector.h"

#include "tpms.h"
#include "tpms_functions.h"

namespace openmc {

//==============================================================================
Expand Down Expand Up @@ -52,6 +56,7 @@ class Surface {
//! \return true if the point is on the "positive" side of the surface and
//! false otherwise.
bool sense(Position r, Direction u) const;
virtual bool is_tpms() const { return false; };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, the base class shouldn't have to have any methods like this. I believe this is not in line with the "dependency inversion principle" which, if followed, means you'll have cleaner object oriented code. do you know of any other way to accomplish this without letting the base class know we're dealing with a TPMS surface?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I tried to do a dynamic casting first, like the following:

Surface* surface = /* some initialization */;

// Attempt to dynamically cast surface to TPMS*
TPMS* tpms = dynamic_cast<TPMS*>(surface);

if (tpms) {
    std::cout << "surface is a TPMS" << std::endl;
} else {
    std::cout << "surface is NOT a TPMS" << std::endl;
}

But it felt even more dirty, and it might hurt cases where there are a lot of surfaces. Moreover, we can probably generalize that feature to allow ray-tracing optimization to other surfaces.

Let me know which solution would be best.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, you shouldn't need to use a dynamic case. ideally, you would just put virtual methods on the surface class that make it behave correctly. dynamic casting means you have done your abstractions poorly. do you have any ideas about what the general logic at hand here might be?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, I do the following in the cell.cpp:

  • check if the surface is a tpms. If it is, I put its token in a list for later. Otherwise I compute the distance.
  • With the distance computed for non-tpms surface I compute the distance for TPMS using the distance of non tpms as a criterium to go out of the ray-tracing loop (going further than that distance is not needed).


//! Determine the direction of a ray reflected from the surface.
//! \param[in] r The point at which the ray is incident.
Expand All @@ -76,7 +81,15 @@ class Surface {
//! \param u The direction of the ray.
//! \param coincident A hint to the code that the given point should lie
//! exactly on the surface.
virtual double distance(Position r, Direction u, bool coincident) const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably leave this as =0. That prevents programmer mistakes and ensures that base classes implement this method, which will surely be unique for each surface type.

Copy link
Author

@pferney05 pferney05 Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to edit that part because of the special treatment of tpms :

cell.cpp l833

const auto& surface = model::surfaces[abs(token) - 1];

// Calculate the distance to this TPMS surface.
bool coincident {std::abs(token) == std::abs(on_surface)};
double d {surface->distance(r, u, coincident, min_dist)};

I am open to another solution but I always failed to create an object surface which is specifically is a SurfaceTPMS or SurfaceFunctionTPMS.

virtual double distance(Position r, Direction u, bool coincident) const
{
return 0.;
};
virtual double distance(
Position r, Direction u, bool coincident, double max_range) const
{
return 0.;
};

//! Compute the local outward normal direction of the surface.
//! \param r A 3D Cartesian coordinate.
Expand Down Expand Up @@ -327,6 +340,55 @@ class SurfaceQuadric : public CSGSurface {
double A_, B_, C_, D_, E_, F_, G_, H_, J_, K_;
};

//==============================================================================
// TPMS surfaces
//==============================================================================

class SurfaceTPMS : public CSGSurface {
public:
explicit SurfaceTPMS(pugi::xml_node surf_node);
bool is_tpms() const { return true; };
double evaluate(Position r) const;
double distance(
Position r, Direction u, bool coincident, double max_range) const;
Direction normal(Position r) const;
void to_hdf5_inner(hid_t group_id) const;

double isovalue, pitch;
double x0, y0, z0;
double a, b, c, d, e, f, g, h, i;
std::string surface_type;

private:
std::list<std::string> tpms_types = {"Schwarz_P", "Gyroid", "Diamond"};
};

//==============================================================================
// Interpolated TPMS surfaces
//==============================================================================

class SurfaceFunctionTPMS : public CSGSurface {
public:
explicit SurfaceFunctionTPMS(pugi::xml_node surf_node);
~SurfaceFunctionTPMS();
bool is_tpms() const { return true; };
double evaluate(Position r) const;
double distance(
Position r, Direction u, bool coincident, double max_range) const;
Direction normal(Position r) const;
void to_hdf5_inner(hid_t group_id) const;

FunctionForTPMS* fPitch;
FunctionForTPMS* fIsovalue;
double x0, y0, z0;
double a, b, c, d, e, f, g, h, i;
std::string surface_type;
std::string function_type;

private:
std::list<std::string> tpms_types = {"Schwarz_P", "Gyroid", "Diamond"};
};

//==============================================================================
//! A toroidal surface described by the quartic torus lies in the x direction
//
Expand Down
Loading