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

Add PointCloud spatial distribution #3161

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from

Conversation

gonuke
Copy link
Contributor

@gonuke gonuke commented Oct 5, 2024

Description

Allows users to define a list of points in space, each with a different relative intensity, to be sampled discretely. This is a valid SpatialDistribution and can be used anywhere that a SpatialDistribution is valid.

Fixes #3159

Checklist

  • I have performed a self-review of my own code
  • I have run clang-format (version 15) on any C++ source files (if applicable)
  • I have followed the style guidelines for Python source files (if applicable)
  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works (if applicable)

@gonuke gonuke marked this pull request as ready for review October 5, 2024 20:08
class PointCloud : public SpatialDistribution {
public:
explicit PointCloud(pugi::xml_node node);
explicit PointCloud(gsl::span<const double> x, gsl::span<const double> y,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: this could just as easily be a vector of Positions but I wasn't sure how to do that in XML space, so decided to just keep it this way for now. Happy for any guidance on changing this to a different form.

Could also overload the constructor with a few choices, but not sure of OpenMC design practices.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think ideally we'd add the points as a vector of Position's. Best approach for this would be to add a function for parsing a set of positions from an XML to the xml_interface.h.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like adding an input stream operator for Position will allow existing code to work, although not sure about robustness

Comment on lines +798 to +802
if isinstance(given_positions, list):
cv.check_length('first position entry', given_positions[0], 3, 3)
self._positions = np.asarray(given_positions)
elif isinstance(given_positions, np.ndarray):
self._positions = given_positions
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another place to seek OpenMC design best practice about being tolerant to lots of data forms for this data

Copy link
Contributor

Choose a reason for hiding this comment

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

The trend seems to be storing NumPy arrays for the internal attributes values when in doubt. I'll let @paulromano chime in here though in case his perspective differs.

Comment on lines 837 to 839
for idx, axis in enumerate(('x','y','z')):
subelement = ET.SubElement(element, axis)
subelement.text = ' '.join(str(e) for e in self.positions[idx,:])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

assuming there is no better way to store a list of points in XML

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could imagine flattening this to a single list of [[x], [y], [z]] or a single list of [ (x,y,z) for zip(x,y,z)], but not sure it's a better choice

double total_strength() { return this->point_idx_dist_.integral(); }

private:
gsl::span<const double> x_, y_, z_;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

could assemble these into a vector of Position on initialization, but not sure pros/cons of that choice vs. keeping these separate...

Copy link
Contributor

Choose a reason for hiding this comment

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

To me it makes sense to use vector<Position> to reduce multiple indexing operations that may be prone to error if changes are made down the line.

Copy link
Contributor

Choose a reason for hiding this comment

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

One more thought, we should make sure to use a container here so the object owns the memory of these attributes -- gsl::span is just a view into memory space and doesn't "own" these values.

Copy link
Contributor

@pshriwise pshriwise 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 this snappy PR @gonuke! A useful distribution that we haven't been able to support before!

class PointCloud : public SpatialDistribution {
public:
explicit PointCloud(pugi::xml_node node);
explicit PointCloud(gsl::span<const double> x, gsl::span<const double> y,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think ideally we'd add the points as a vector of Position's. Best approach for this would be to add a function for parsing a set of positions from an XML to the xml_interface.h.

double total_strength() { return this->point_idx_dist_.integral(); }

private:
gsl::span<const double> x_, y_, z_;
Copy link
Contributor

Choose a reason for hiding this comment

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

To me it makes sense to use vector<Position> to reduce multiple indexing operations that may be prone to error if changes are made down the line.

Comment on lines 778 to 779
psoitions: numpy.ndarray (3xN)
The points in space to be sampled
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
psoitions: numpy.ndarray (3xN)
The points in space to be sampled
positions: numpy.ndarray
The points in space to be sampled with shape (N, 3)

return cls(positions, strengths)



Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

"""
coord = {}

for axis in ('x','y','z'):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking the XML format of the data as a flat list of points (X1, Y1, Z1, X2, Y2, Z2, ...) would make the import export code simpler, and it isn't more or less readable in an XML format (for my brain anyway).

double total_strength() { return this->point_idx_dist_.integral(); }

private:
gsl::span<const double> x_, y_, z_;
Copy link
Contributor

Choose a reason for hiding this comment

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

One more thought, we should make sure to use a container here so the object owns the memory of these attributes -- gsl::span is just a view into memory space and doesn't "own" these values.


for idx, axis in enumerate(('x','y','z')):
subelement = ET.SubElement(element, axis)
subelement.text = ' '.join(str(e) for e in self.positions[idx,:])
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
subelement.text = ' '.join(str(e) for e in self.positions[idx,:])
subelement.text = ' '.join(str(e) for e in self.positions[..., idx])

I think?

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.

@gonuke Thanks for this PR! Out of curiosity, what is the intended application of this feature?

@gonuke
Copy link
Contributor Author

gonuke commented Oct 10, 2024

We have a user who has an existing approximation of a volumetric source by a high density list of isotropic point sources. They want to use that source for comparison to other simulations.

@gonuke
Copy link
Contributor Author

gonuke commented Oct 12, 2024

While it may have been elegant to add an istream operator for Position the version I added did not round-trip with the existing ostream operator and it is more convenient in XML to not use the verbose ostream format.

Thus, I've relied on just reading a list of doubles and packing them into Position in the reader.

Copy link
Contributor

@pshriwise pshriwise left a comment

Choose a reason for hiding this comment

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

Some small line suggestions here @gonuke. I went ahead and added test that samples the external source distribution on the C++ side and factored out a function in the XML interface for parsing a set of Positions from a flat array of values on a node. Hope that's okay!

Thanks again for this addition.

docs/source/usersguide/settings.rst Outdated Show resolved Hide resolved
openmc/stats/multivariate.py Outdated Show resolved Hide resolved
openmc/stats/multivariate.py Outdated Show resolved Hide resolved
Comment on lines +794 to +795
if given_positions is None:
raise ValueError('No positions were provided')
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I'd opt to let the check_iterable_type throw an error here instead.


element.set('type', 'cloud')

subelement = ET.SubElement(element, 'coords')
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the XML IO format spec will need to be updated to include this option.

src/distribution_spatial.cpp Outdated Show resolved Hide resolved
openmc/stats/multivariate.py Outdated Show resolved Hide resolved
pshriwise and others added 6 commits October 16, 2024 10:10
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow sampling source location from a point cloud with discrete probabilities
3 participants