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

Resonace Covariance Module #1024

Merged
merged 59 commits into from
Aug 3, 2018
Merged

Resonace Covariance Module #1024

merged 59 commits into from
Aug 3, 2018

Conversation

icmeyer
Copy link
Contributor

@icmeyer icmeyer commented Jul 17, 2018

This PR incorporates into the API the ability to parse resonance covariance data from ENDF File 32. Its intended functionality can be seen in the new example notebook: openmc/examples/jupyter/nuclear-data-resonance-covariance.ipynb
Namely:

  • Parse File 32 data
  • Allow for selection of a subset of the parameters and corresponding covariance data
  • Sampling of resonance parameters (currently uses numpy.random.multivariate_normal)
  • Reconstruction of the cross section using sampled resonance parameters

My first PR so comments on style encouraged!

@icmeyer
Copy link
Contributor Author

icmeyer commented Jul 20, 2018

Addressed the comments by @paulromano. Added in warning on the sampling method that it can produce nonphysical negative parameters which may cause reconstruction to fail. This is because the covariance matrix assumes a multivariate normal distribution from which negative numbers may be drawn. Further discussion and possible solutions to this issue can be found in a Rochman paper here.

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.

Here are a few more comments on the PR. I would also recommend running pycodestyle on your resonance_covariance.py to address style issues.

@@ -71,6 +71,24 @@ def float_endf(s):
return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s))


def int_endf(s):
"""Conver string to int. Used for INTG records where blank entries
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: convert

@@ -71,6 +71,24 @@ def float_endf(s):
return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s))


def int_endf(s):
Copy link
Contributor

Choose a reason for hiding this comment

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

Change to _int_endf since it is unlikely a user would ever need this

@@ -249,6 +267,50 @@ def get_tab2_record(file_obj):

return params, Tabulated2D(breakpoints, interpolation)

def get_intg_record(file_obj):
Copy link
Contributor

Choose a reason for hiding this comment

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

Add another blank line before this function (PEP8: 2 lines between functions/classes at top level)

corr = corr + corr.T - np.diag(corr.diagonal())
return corr


Copy link
Contributor

Choose a reason for hiding this comment

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

delete one blank line here

self.cov_subset = cov_subset

def sample_resonance_parameters(self, n_samples, resonances, use_subset=False):
"""Return a list size 'n_samples' of openmc.data.ResonanceRange objects.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would say something more like "Sample resonance parameters based on the covariances." The type of the object returned (and its length) are described in the "Returns" section so probably not necessary to mention them here too.

res_range.parameters = sample_params
samples.append(res_range)

self.samples = samples
Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed before, the samples object should be returned directly rather than being set as an attribute of the current instance.

cov_subset[tri_indices] = cov_subset_vals

self.parameters_subset = parameters_subset
self.cov_subset = cov_subset
Copy link
Contributor

Choose a reason for hiding this comment

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

As with the sampling method, this method should return an object rather than setting attributes of the current instance. I think what would make the most sense is to return a new instance of ResonanceCovarianceRange with the specified subset of parameters/covariance matrix. This would allow you to get rid of the use_subset argument on the sampling method since calling sample_resonance_parameters on the new instance would use the subset by default.

"cell_type": "markdown",
"metadata": {},
"source": [
"The covariance module also has the ability to sample a new set of parameters using the covariance matrix. Currently the sampling uses np.multivariate_normal(). Because parameters are assumed to have a multivariate normal distribution this method doesn't not currently guarantee that sampled parameters will be positive."
Copy link
Contributor

Choose a reason for hiding this comment

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

np.multivariate_normal -> numpy.multivariate_normal

items : list
Items from the CONT record at the start of the resonance range
subsection
file2params : openmc.data.ResonanceRange object
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 is still actually taking a DataFrame of resonance parameters rather than the ResonanceRange object that owns it. I think it would be better to use the ResonanceRange object directly and store it as an attribute of the ResonanceCovarianceRange instance. That way, when sample_resonance_parameters is called, there would be no need to pass the parameters as they could be retrieved from the attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment applies to ReichMooreCovariance

"cell_type": "markdown",
"metadata": {},
"source": [
"The sampling routine requires the incorpotation of the `openmc.data.ResonanceRange` for the same resonance range object. This allows each sample itself to be its own `openmc.data.ResonanceRange` with a new set of parameters. Looking at some of the sampled parameters below:"
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: incorpotation

parameters = self.parameters
cov = self.covariance
# Copy ResonanceRange object
res_range = copy.copy(self.file2res)
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now, you're creating a single copy of the resonance range. If multiple samples are requested, this method actually returns a list with the same object repeated N times (this is evident in the Jupyter notebook where you can see that two different samples have the same parameters). You should be creating N copies of the original object and then setting the parameters appropriately for each one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Saw similar code being used for each formalism/mpar so thought I could pull it out front but guess copy.copy() and .append() behaved differently than I thought. Should be better now.

'captureWidth', 'fissionWidth', 'competitiveWidth']
sample_params = pd.DataFrame.from_records(records,
columns=columns)
res_range.parameters = sample_params
Copy link
Contributor

Choose a reason for hiding this comment

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

Related to the previous comment, this line overwrites the parameters on each iteration of the for loop. (also, I don't see a similar line updating the parameters for the mpar=4/5 and the RM cases)

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.

Ok, hopefully this is the last round!

@@ -0,0 +1,743 @@
from collections import defaultdict, MutableSequence, Iterable
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove defaultdict, Iterable (not used)

mpar : int
Number of parameters in covariance matrix for each individual resonance
formalism : str
String descriptor of formalism
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to add description of file2res here (since it is assumed to be present for any subclass)

self.energy_min = energy_min
self.energy_max = energy_max

def res_subset(self, parameter_str, bounds):
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about just naming this subset? The res seems kind of superfluous to me.

gn = sample[1::mpar]
gg = sample[2::mpar]
gfa = sample[3::mpar] if mpar > 3 else parameters['fissionWidthA'].values
gfb = sample[3::mpar] if mpar > 3 else parameters['fissionWidthB'].values
Copy link
Contributor

Choose a reason for hiding this comment

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

4::mpar


# Add parameters from File 2
parameters = _add_file2_contributions(parameters,
resonance.parameters)
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 the last few lines here (starting with columns = ...) are the same for each branch of the if -- I think those could be moved out of the if altogether (so that they're not repeated). The only thing that's different is in the lcomp == 1 case, mpar is not set, but that looks like a bug since it is used in the call below.


# Add parameters from File 2
parameters = _add_file2_contributions(parameters,
resonance.parameters)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here -- the lines starting from columns = ... looks redundant on the branches of the if.

@@ -39,7 +39,7 @@ def sm150():
def gd154():
"""Gd154 ENDF data (contains Reich Moore resonance range)"""
filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-064_Gd_154.endf')
return openmc.data.IncidentNeutron.from_endf(filename)
return openmc.data.IncidentNeutron.from_endf(filename, covariance = True)
Copy link
Contributor

Choose a reason for hiding this comment

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

covariance=True

res_cov_range.covariance = cov_subset
return res_cov_range

def sample_resonance_parameters(self, n_samples):
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about naming this simply sample()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense to me. Wasn't sure how descriptive the names needed to be but as with res_subset I think that having them as methods of the objects provides enough context

@icmeyer
Copy link
Contributor Author

icmeyer commented Aug 1, 2018

It looks like the build is failing with Python 3.4 on the following test.
https://github.com/mit-crpg/openmc/blob/ac57dd929841026bb91cd1f89d67b5556b4ad157/tests/unit_tests/test_capi.py#L89-L100
Running on my machine with 3.4 gives me
> assert m.densities == pytest.approx(test_dens)
E ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Does this test need to be changed? Also odd that on some of the builds Travis isn't showing the final report of pytest

@paulromano paulromano merged commit cbb9ace into openmc-dev:develop Aug 3, 2018
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.

2 participants