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

Refactor and generalize the boundary condition implementation #1701

Merged
merged 22 commits into from Dec 22, 2020

Conversation

smharper
Copy link
Contributor

This PR gives an overhaul of the core boundary condition software. Mostly, this is to clean-up and generalize the periodic BCs.

The old periodic BC implementation was kind of messy. The same PeriodicSurface::periodic_translate function was used for both rotational and translational BCs, and this function would redundantly check during particle transport which type of periodicity was relevant. The old implementation also made some unchecked assumptions about the types of surfaces that were paired via these BC's, and it relied on several dynamic_cast calls during particle transport.

This PR cleans up much of that by moving most of the BC logic into a new BoundaryCondition class. Surfaces are now assigned a pointer to a BoundaryCondition object, and the different BC types are handled by different virtual functions rather than an enum switch-case statement. The periodic BCs are split into the distinct TranslationalPeriodicBC and RotationalPeriodicBC classes.

These changes generalize the periodic BCs a bit. We can now handle rotations with the general SurfacePlane object instead of just SurfaceXPlane/SurfaceYPlane pairs. This means we can now model 3-fold, 6-fold, etc. rotational periodicity as well as 4-fold. (Although it is still limited to rotations about the z-axis at x=0, y=0). For periodic BCs, there is also now a lot more checking done during initialization to make sure the user hasn't specified some un-expected/non-implemented case.

@gridley
Copy link
Contributor

gridley commented Oct 20, 2020

Tiny note: you don't need to explicitly initialize any of the C++ smart pointers to nullptr, since that is automatic and already in their default constructors.

@smharper
Copy link
Contributor Author

Tiny note: you don't need to explicitly initialize any of the C++ smart pointers to nullptr, since that is automatic and already in their default constructors.

Fair, but I was thinking it'd be good to be explicit about this and give a sort of hint to other other devs that nullptr is an expected value for that pointer and must be handled. I guess I'm drawing a bit on the Zen of Python here.

@gridley
Copy link
Contributor

gridley commented Oct 20, 2020

Fair enough. It's stylistic, end of the day. There is a fairly different zen written by a former lead developer of MCNP I have found myself subscribing to lately, although I can't remember where I once read it. The essence of it is that if you write your code concisely such that the whole of the algorithm you're writing fits on the screen all at once, it's more clear to see the overall flow of the code. But then again... is anything written in fortran worth looking up to?

Copy link
Contributor

@paulromano paulromano left a comment

Choose a reason for hiding this comment

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

Very nice reorganization @smharper, and it's great that this also adds support for generic rotational symmetry. Here's my first cut at a review -- I'm going to do some sanity checks with simple models as well so I might have another round of feedback.

include/openmc/boundary_condition.h Outdated Show resolved Hide resolved
include/openmc/boundary_condition.h Outdated Show resolved Hide resolved
include/openmc/boundary_condition.h Outdated Show resolved Hide resolved
include/openmc/boundary_condition.h Outdated Show resolved Hide resolved
include/openmc/boundary_condition.h Outdated Show resolved Hide resolved
src/surface.cpp Outdated
Comment on lines 1120 to 1126
struct is_unresolved_pair {
bool operator()(const std::pair<int, int> p) const
{return p.second == -1;}
};
is_unresolved_pair unresolved_f;
auto first_unresolved = std::find_if(periodic_pairs.begin(),
periodic_pairs.end(), unresolved_f);
Copy link
Contributor

Choose a reason for hiding this comment

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

Using a lambda might make this a bit cleaner

src/surface.cpp Outdated
Comment on lines 1151 to 1155
Direction norm1 = surf1.normal({0, 0, 0});
Direction norm2 = surf2.normal({0, 0, 0});
double dot_prod = norm1.dot(norm2);

if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add some comments explaining the logic here

src/surface.cpp Outdated
if (check_for_node(surf_node, "periodic_surface_id")) {
int i_periodic = std::stoi(get_node_value(surf_node,
"periodic_surface_id"));
periodic_pairs.push_back({model::surfaces.back()->id_, i_periodic});
Copy link
Contributor

Choose a reason for hiding this comment

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

If we were to store the pairs of values such that the lesser (or greater) value comes first, you could use a set and thereby avoid the elimination of duplicates further down

Comment on lines 46 to 56
plots = openmc.Plots()

plot = openmc.Plot()
plot.basis = 'xz'
plot.origin = (2.5, 2.5, 0)
plot.width = (6, 11)
plot.pixels = (300, 300)
plots.append(plot)

plots.export_to_xml()

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably can remove this?

tests/regression_tests/periodic/test.py Show resolved Hide resolved
Comment on lines 203 to 205
double theta1 = std::atan2(norm1.y, norm1.x);
double theta2 = std::atan2(norm2.y, norm2.x);
angle_ = theta2 - theta1;
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this doesn't work for all cases. I tried doing a 1/6th rotational symmetry problem with the following:

plane1 = openmc.YPlane(boundary_type='periodic')

angle = pi/3
p1 = (0., 0., 0.)
p2 = (cos(angle), sin(angle), 0.)
p3 = (0., 0., 1.)
plane2 = openmc.Plane.from_points(p1, p2, p3, boundary_type='periodic')
plane2.periodic_surface = plane1

Note that for plane 2, the normal is (p2 - p1) x (p3 - p1) which results in a direction that is down and to the right. Here is what you end up with (planes in green, normals in blue):
wrong_angle
As you can see, we end up with an angle between the normals that is 2π/3 instead of π/3 as desired. The end result is that when you try to run a model like this, all particles that cross the periodic boundary get lost. If you swap p2 and p3 in the definition of the plane, the normal will go in the other direction, the calculated angle is π/3, and everything works as expected.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, thinking about this a little more, if instead of switching the planes, I use the region below the y-plane (which gives me 1/3rd rotational symmetry), the simulation works fine because in that case the calculated angle matches the region. So this implies that how the angle is calculated depends on how the surfaces are actually used to create a cell, which seems awkward. Maybe we need some sort of convention for how a model must be constructed if rotational periodic BCs are used?

Copy link
Contributor Author

@smharper smharper Oct 27, 2020

Choose a reason for hiding this comment

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

I haven't figured out a good solution to that problem yet. When I wrote this code, I was operating off the assumption that one normal vector would normally point inward, and the other outward. (This is likely what happens if you have an azimuthally segmented problem and you define all the planes from the general Plane class in a loop, as was done in the problem that inspired this PR.) But this assumption seems to be false often enough.

Perhaps we'll have to specify a convention, but I'll keep thinking to see if we can come up with something easy. Maybe we can compute the bounding box of the root universe and check it against the normals of the bounding planes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good -- let me know if you come up with something clever. The bounding box idea sounds a little complicated, so if it's too difficult we can always just adopt some convention that is expected to be obeyed. I think as long as it is well documented, then at least we have something we can point to if (and when) a user messes it up.

@smharper
Copy link
Contributor Author

I've abandoned the attempt now to try and resolve where the geometry lies with respect to the periodic normal vectors. Here it'll always be assumed that the normals point inwards towards the geometry (like with an XPlane and YPlane in the first quadrant).

I've added some notes to the docs to explain this, and I've also added a note about normals to the lost particle message to hint users on that point.

@paulromano
Copy link
Contributor

@smharper Sorry for the delay here -- I tried my 1/6th rotational periodic case again and it still doesn't work (even though I believe my normals are inward facing). It looks like the calculation of the angle is still the same, in which case it would result in an angle of 2π/3 instead of π/3 for this case.

@smharper
Copy link
Contributor Author

Phew, this geometry problem is surprisingly tricky. I think those last commits should do the trick.

Copy link
Contributor

@paulromano paulromano left a comment

Choose a reason for hiding this comment

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

Thanks for bearing with me. I tested once more on my own 1/6th periodic model and it worked great this time. Answers match an equivalent problem without periodicity.

@paulromano paulromano merged commit 1071549 into openmc-dev:develop Dec 22, 2020
@smharper
Copy link
Contributor Author

No worries on the delay. I know how it is! Glad I finally implemented this though. I've wanted to do something like it from the very beginning of the C++ rewrite.

Now I think the only little quibble from the C++ rewrite that I have left is the surface indexing scheme. It's unsatisfying that we always add one to get a signed surface index. I wonder if we can make an efficient class that essentially does the same thing (uses the lower 31 bits for the actual index and then the 32nd bit indicates halfspace) but wraps it in nicer syntax.

@paulromano
Copy link
Contributor

@smharper Yes, I think a simple wrapper class for the surface indexing like what you propose would be great! The off-by-one issue bothers me too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants