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

Adding GENDF Capabilities to ENDFtk #73

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft

Conversation

nathangibson14
Copy link
Contributor

This pull request adds the ability to read a GENDF file via the syntaxTree and to do basic parsing of cross section (MFD 3) and transfer matrix (MFD 6) sections. This only supports reading GENDF files; writing is not yet implemented.

The classes of the syntaxTree are now tagged as ENDF or GENDF, and ENDF is default. This means there's no change to the existing syntax for reading ENDF files. Rather than specify the tag directly, users (and other places in the code) can use convenience aliases such as GendfTape.

Parsing is currently limited to extracting a cross section or matrix element by group, Legendre order, and dilution index. Adding capability for an array of cross sections will likely follow sooner than later.

There is plenty more to do for full GENDF support, but I believe this is enough to start using while other functionality is developed. Things to include later include:

  • Support for header data (MFD 1) and delayed neutron data (MFD 5)
  • Support for ratio quantities (MFD/MT 3/452 and various others)
  • Support for compressed fission transfer matrix format (prod/spec for lower energy groups)
  • Support for photon production
  • More convenient data structures from parsed data structures.

The test suite passes, although there are plenty of ranges-related test suite quirks on various systems/compilers. None of the quirks touch pieces of the code affected by this PR. I found issues with two tests on Snow/GCC 8.3.0 and one test on my MacBook/GCC 9.2.0. I'll put up a separate issue, but it's time to consider upgrading the ranges package for a variety of reasons.

Let me know how my biggest foray into C++ looks! 😄

long int lineNo = 2;

THEN( "the object can be created" ) {
GendfDataRecord record( begin, end, lineNo, 1, 1, 9228, 3, 2 );
Copy link
Member

Choose a reason for hiding this comment

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

Is there any kind of data that someone might try to use that would be invalid? We should try and capture that and throw some kind of exception (std::exception is fine). We should also test for that here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is really just a wrapped ListRecord. I think most of the error handling is covered there.

Copy link
Member

@jlconlin jlconlin left a comment

Choose a reason for hiding this comment

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

Looks pretty good. I did not look at this for the technical correctness of a GENDF file (not familiar with it to make a judgement). I was mostly looking for C++ and style issues.

It's a good first Pull Request. Clearly you have taken after @whaeck—which isn't a bad thing, especially since ENDFtk is his project.

template<>
class GendfType< 3 > : protected GendfBase {

protected:
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't need to be here with the way the class is currently configured.


// TODO: Add support for ratio quantities
if( data_.at(num_groups_).NG2() == 3 ) {
Log::info( "ENDFtk does not yet support ratio quantities." );
Copy link
Member

Choose a reason for hiding this comment

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

If you are going to throw an exception, Log an error. You can provide informational logs afterwards as needed.

The practice we have developed is we log an error when it is initially thrown. If we catch (and possibly re-throw) an exception, then we log an informational message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not convinced this is done consistently throughout, but I'm happy to change mine. What you are suggesting makes more sense to me, I was just trying to stick to the convention I saw in similar places, which likely was non-conforming.

Copy link
Member

Choose a reason for hiding this comment

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

The way we've done it up to now is that an error is logged when the original exception is created, logging information later on to indicate where it happened, etc. by catching the exception.

* @param order Legendre order
* @param idil Index of dilution (0-based)
*/
double getCrossSection( int group,
Copy link
Member

Choose a reason for hiding this comment

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

Ugh. I hate getters (or setters) that start like get... (or set...). The get is unnecessary. You can tell the difference between a getter and a setter based on the function arguments. Granted, this is a matter of opinion (mine, in fact), but it is what we have adopted for NJOY. See Section 6 of our Style Guide.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like Python where I don't need () on my getters and setters 😄

I usually am with you. This one was because I was thinking of getCrossSectionByGroup versus getCrossSection returning the whole array. I'm pretty sure I wrote those routines into OpenMOC years ago, so they came back to me. I didn't even stick with that naming convention, so I won't argue in favor for get here!

std::string getSection();


SCENARIO( "creating a GendfBase object" ) {
Copy link
Member

Choose a reason for hiding this comment

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

You are not creating a GendfBase object here. You are creating a GendfType<3>. Yeah, I'm being pedantic. I don't really care what is said here, but we should try to be accurate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy and paste can be cruel. I'll fix it.

HeadRecord head(begin, end, lineNo);

THEN( "the object can be created" ) {
section::GendfType<3> chunk(head, begin, end, lineNo, MAT );
Copy link
Member

Choose a reason for hiding this comment

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

chunk? I see you've taken after @whaeck in your naming convention

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally had section, but felt dirty using section as both a namespace and a variable at the same time. And when I looked to other tests for guidance, voila -- I found @whaeck's approach.

Copy link
Member

Choose a reason for hiding this comment

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

Because that's what it is: a chunk of ENDF data :-p

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my Python bindings, I've been using parsec for parsed sections. I'm sure our friends in astrophysics would approve of that.


// check if group was already seen
if (data_records.count( group ) > 0) {
Log::info( "Group {} appears multiple times in MFD/MT {}/{}",
Copy link
Member

Choose a reason for hiding this comment

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

Log::error

std::string invalidSEND();


SCENARIO( "creating a GendfBase object" ) {
Copy link
Member

Choose a reason for hiding this comment

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

Here you are testing a GendfBase.

/* methods */

// this file implements the parse() function
#include "ENDFtk/syntaxTree/Section/src/parse-gendf.hpp"
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this could be implemented with a templated parse function:

template< typename Tag >
void parse< Tag >(...);

template<>
void parse< GENDFTag >(...){}

template<>
void parse< ENDFTag >(...)

This way, you don't need to specialize the class, just the parse function. It seems that everything else is the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really wanted to do this, but I couldn't figure out a way to make it happen. A tag with a default value doesn't play nice with the int... OptionalMT that parse is already templated on. I also don't want the user to have to specify the tag.

If you can see a way to clean this up, I'm all for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In talking with Wim this morning, I think I found a cleaner solution in line with what you were saying. Stay tuned!


} // WHEN

WHEN( "an invalid (MT!=0) SEND record ends the Section" ){
Copy link
Member

Choose a reason for hiding this comment

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

Good job testing failure conditions. Are there others that also need to be tested?

@@ -0,0 +1,71 @@
/* Template specialization for GendfMaterial */
template< typename BufferIterator >
class Material< BufferIterator, GENDFTag > {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I see the reason for a separate GENDF Material. (Granted I didn't look real close.) It seems it is virtually identical to the Material class, just uses the GENDFTag. What exactly is the difference and can we do template specialization on a member function instead of the entire class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a subtle but big difference between a GendfMaterial and a Material. A Material is processed by looping over the Files it contains, creating a map of these File objects. In a GENDF file, there is no guarantee that all sections with the same MFD are consecutive. And to make matters worse, there's no guarantee that a MFD/MT section is unique (although I'd hope the repeated section is at least identical!).

So I implemented this so that there is no GendfFile, and the GendfMaterial contains a map of GendfSection objects directly.

There are lots of places where the ENDF format is frustrating, but GENDF is much more frustrating despite having a much smaller spec!

@whaeck
Copy link
Member

whaeck commented Jun 3, 2020

Stop poking me ;-)

@nathangibson14
Copy link
Contributor Author

@whaeck Poke.

Copy link
Member

@whaeck whaeck left a comment

Choose a reason for hiding this comment

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

I started looking at this, there's already a few points of design we could discuss in more detail. I'll continue looking at this when I have time this week.

@@ -0,0 +1,46 @@
class GendfDataRecord : protected ListRecord {

protected:
Copy link
Member

Choose a reason for hiding this comment

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

There's no need to make these members protected.

/* getters */
double temperature() const { return this->C1(); };
int NG2() const { return this->L1(); }
int numSecondaryPositions() const { return NG2(); }
Copy link
Member

Choose a reason for hiding this comment

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

this->NG2()

*/
auto flux() const {
return this->list()
| ranges::view::take_exactly( num_legendre_ * num_sigma0_ );
Copy link
Member

Choose a reason for hiding this comment

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

this is where you would use those public methods.

int MT )
: ListRecord( it, end, lineNumber, MAT, MF, MT ),
num_legendre_( num_legendre ),
num_sigma0_( num_sigma0 ) {}
Copy link
Member

Choose a reason for hiding this comment

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

You still need a constructor to create this from scratch, especially if we want to create GENDF files. It's better to add it now instead of waiting.

Comment on lines +5 to +6
int num_legendre_;
int num_sigma0_;
Copy link
Member

Choose a reason for hiding this comment

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

I would rename these and provide public methods to retrieve them. As for renaming, sigma0 is a bit obscure so it my be more appropriate to use dilutions instead. There's also no need to use abbreviations in these names (number instead of num).

Comment on lines +37 to +39
* - vector cross sections (NLxNZ)
* - ratio and cross sections (both NLxNZ)
* - matrix elements (NLxNZxNG)
Copy link
Member

Choose a reason for hiding this comment

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

From this comment, I assume that there are essentially three types of records, which could all have their own interface? I would surmise that the matrix element one is only used in MFD6 data. Where are the other ones used?


protected:
/* fields */
std::map< int, GendfDataRecord > data_;
Copy link
Member

Choose a reason for hiding this comment

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

What about using a template for this instead of GendfDataRecord? The GendfDataRecord can store different types of data (just like a ListRecord does). We may want to explore making specific classes that inherit from GendfDataRecord to provide a more convenient interface to their particular type of data and then using it as the template argument.

You could also add more convenience functions in this base class to see if a specific group has a record or not, etc. to avoid having to do this each GendfType class (see getCrossSection() where you verify if a group is present). By combining these together with the convenience interface for a group cross section, you could write code like this that is more compact and easier to read:

template < typename Record >
class GendfBase : protected Base {

  std::map< unsigned int, Record > records_;

public:

  // this gets you access to the records as if they were stored in a vector
  auto records() const { return ranges::view::values( this->records_ ); }

  // this gets you access to the a record by group index
  auto record( unsigned int group ) const { return this->records_ .at( group ); }

  // this verifies if the group is present
  auto hasRecord( unsigned int group ) const {

    return this->records_ .find( group ) != this->records_ .end();
  }
};

class GendfType< 3 > : protected GendfBase< GendfGroupCrossSection > {

public:

  auto groups() { return this->records(); }
  auto group( unsigned int group ) { return this->record( group ); }

  // this gets you the full multi-group cross section
  // this still needs dilution, but you get the gist
  auto crossSection() const {

    return ranges::view::iota( 0, number_groups )
             | ranges::view::transform(
                   [&] ( unsigned int group ) -> double
                       { return this->hasRecord( group ) ? this->group( group ).crossSection()
                                                         : 0.0; } );
  }
}

@nathangibson14 nathangibson14 marked this pull request as draft January 14, 2021 15:55
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.

3 participants