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

Specifying multiple particle output formats #1049

Closed

Conversation

hlokavarapu
Copy link
Contributor

Using the container vector of output::interfaces, can now generate the output of multiple formats that are specified by the parameter "Data output format."

As per edge cases, if user specifies:
set Data output format = ascii, hdf5, none
then, none is honored.

If user specifies:
set Data output format = ascii, ascii, vtu, vtu
then, ascii and vtu data are generated only once.


for ( typename std::vector<std_cxx11::shared_ptr<Output::Interface<dim> > >::const_iterator itr= output.begin(); itr != output.end(); itr++)
{
filename = (itr->get()->output_particle_data(particles,
Copy link
Contributor

Choose a reason for hiding this comment

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

do you need the ->get() part of this? I think you could get away by just saying itr->output_particle_data.

Also just remove the outer parentheses around the entire call.

Copy link
Contributor

Choose a reason for hiding this comment

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

On a separate note, you overwrite the filename variable each time. How about creating a list of filenames that, with comma separators, are ultimately going to be returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since output is a vector of shared pointers, we dereference once to a shared pointer to output interface, and must dereference again to access the output interface object. itr->get()-> is necessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. I had thought that the compiler just inserts deference operations until it actually gets to an object without such an operator. I may misremember.

Copy link
Contributor

Choose a reason for hiding this comment

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

I misremembered.

@bangerth
Copy link
Contributor

/run-tests

@bangerth
Copy link
Contributor

Could you clone one of the existing tests that output particle information so that it writes in two or more formats?

@@ -85,19 +85,26 @@ namespace aspect
std::string
World<dim>::generate_output() const
{
std::vector<std::string> filenames;
Copy link
Member

Choose a reason for hiding this comment

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

What do you use this vector for? Do you intend to return all file names? But what to do with them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, this was a leftover fragment of code. I originally intended to return a vector of file names but later realized that there wasn;t an immediate and clean way of writing these to the statistics file.

@bangerth
Copy link
Contributor

Let us know when you have updated the patch!

@@ -16,40 +11,20 @@ Number of degrees of freedom: 4,645 (2,178+289+1,089+1,089)
Solving Stokes system... 30+3 iterations.

Postprocessing:
Writing particle output: output/particles/particles-00001.{txt,vtu,hdf5}
Writing particle output: output-particle_output_multiple_formats/particles/particles-00000.{txt,h5,vtu}
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 the output format that I have currently implemented, particles-00000.{txt,h5,vtu}.
If only one format is specified, for example ascii, the string is set to particles-00000.txt.
Is this clear cut?

@hlokavarapu
Copy link
Contributor Author

Reviving a two month old PR that I have recently updated in order to touch base on the syntax for the particle output logging messages. Thanks @gassmoeller, @bangerth, @egpuckett. If we can finalize on the syntax, I can then proceed to update tests that are using particles.

@hlokavarapu hlokavarapu force-pushed the Multiple_particle_output branch 2 times, most recently from 963deed to 8d734e1 Compare May 14, 2017 17:47
@hlokavarapu
Copy link
Contributor Author

@gassmoeller , this PR is ready to be reviewed!

Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

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

I'm not a big fan of the repetitiousness of this patch. Each particle output generator gets functions get_file_name(), get_particle_output_location(), and get_file_index(), but I think all of this could actually be handled in the base class if only a derived class could provide a file suffix (via an abstract function in the base class that derived classes have to overload -- or maybe more elegantly through an argument that derived classes pass to the constructor of the base class).

I think such a change would save a couple of hundred lines of repetitive code already.

@hlokavarapu hlokavarapu force-pushed the Multiple_particle_output branch 3 times, most recently from c85ba34 to 204b49c Compare May 16, 2017 16:40
@hlokavarapu
Copy link
Contributor Author

PR is ready to be reviewed once more, (only took two days to get the abstraction correct) 😄

@hlokavarapu
Copy link
Contributor Author

hlokavarapu commented May 16, 2017

Previously, this PR would trigger the tester, however, this is no longer the case now... Why is this?

@bangerth
Copy link
Contributor

/run-tests

Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

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

More comments coming, but this for now.

*/
unsigned int file_index;
const std::string
get_file_name ();
Copy link
Contributor

Choose a reason for hiding this comment

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

It makes no difference whether the string you return here is const or not. const only makes a difference for reference and pointer types when returned from a function. Can you remove the qualification?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, since the function is virtual (because it is virtual in the base class), please mark it as virtual.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair point, fixed.

*/
virtual
const std::string
get_file_name ();
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Contributor

Choose a reason for hiding this comment

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

what does the default implementation of this class do? If the base class doesn't know what to do, then mark the function =0.

Also, is the function supposed to change member variables? If not, please mark the function const.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The default implementation is

     template <int dim>
      std::string
      Interface<dim>::get_file_name ()
      {
        return "particles-"
               + get_file_index()
               + "."
               + Utilities::int_to_string(Utilities::MPI::this_mpi_process(this->get_mpi_communicator()), 4)
               + output_file_suffix;
      }

Right, if the return type is not a pointer, then it shouldn;t be of type const but if the function itself doesn;t modify any provate or protected variables, then the function is of type const.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, but then you need to write the declaration as

  virtual std::string get_filename() const;

Are any of the derived classes going to actually implement a different behavior than the base class?

Also, please document the default behavior of the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HDF5Output class does override this function as

      template <int dim>
      std::string
      HDF5Output<dim>::get_file_name () const
      {
        return "particles-"
               + Interface<dim>::get_file_index ()
               + this->output_file_suffix;
      }

mainly because at the moment, when running with multiple MPI processes, a single output file is written as if grouped files is set to 1. This isn;t the case for txt and vtu based output.

Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

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

More comments.


return output_file_prefix;
return "txt";
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't make much sense. The text is posted on the screen, so the real file name would be a useful thing to have. Or do you collate it later on from all output processors? If the latter, can you update the documentation of this function (in the base class) to explain what they are supposed to return? Or get the suffix from the base class via a new member function, given that the base class already knows the suffix?

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 also am not fond of the return string but in essence, the return stings are being collected and formatted to produce a nice readable string, ie.

*** Timestep 0:  t=0 seconds
   Skipping temperature solve because RHS is zero.
   Solving C_1 system ... 0 iterations.
   Rebuilding Stokes preconditioner...
   Solving Stokes system... 34+0 iterations.

   Postprocessing:
     Writing particle output: output/particles/particles-00000.{txt,vtu,h5}

I have updated the documentation in all of the header files in include/aspect/particle/output directory. Primarily, I specify that the return type of the function

         virtual
          std::string
          output_particle_data(const std::multimap<types::LevelInd, Particle<dim> >     &particles,
                               const Property::ParticlePropertyInformation &property_information,
                               const double current_time);

is a string that contains the file extension, i.e.:

           * @return The name of the file extension that was written, or any other
           * information that describes what output was produced if for example
           * multiple files were created.
           */

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, that works.

;
}
void ASCIIOutput<dim>::serialize (Archive &, const unsigned int)
{}
Copy link
Contributor

Choose a reason for hiding this comment

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

If some of the derived classes don't need to implement anything, then maybe move this function into the base class and only overload it in the derived classes if they need to do anything non-standard?

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 going to note that this is now actively incorrect because you don't serialize the member variables of the base 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.

I have moved the the function serialize into the base class, interface, and declared it as a virtual. Serializing the member variables, i.e:

      template <int dim>
      template <class Archive>
      void Interface<dim>::serialize (Archive &ar, const unsigned int)
      {
        ar &output_file_suffix
        &file_index
        ;
      }

In terms of serialization, is it a top down model in that the parent classes serialize function is called followed by the serialize function of the childs class?

I am planning on running the debugger to get to the bottom of this question.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can't make template functions virtual. You can only declare a function of the same name in a derived class, i.e., have the same template function in the derived class as in the base class. The function in the derived class would then serialize its own variables and then call the corresponding function in the base class that would serialize the variables in the base class (or do it the other way around).

template <int dim>
HDF5Output<dim>::HDF5Output()
:
file_index(0)
Interface<dim> ()
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this line -- it is implied.

@@ -44,23 +46,68 @@ namespace aspect
template <int dim>
void
Interface<dim>::initialize ()
{}
{
file_index = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

move this into the member initializer list of the constructor. initialize() would then just be an empty function


template <int dim>
Copy link
Contributor

Choose a reason for hiding this comment

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

add two more empty lines here

@@ -32,14 +32,15 @@ namespace aspect
template <int dim>
VTUOutput<dim>::VTUOutput()
:
file_index(0)
Interface<dim> ()
{}
Copy link
Contributor

Choose a reason for hiding this comment

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

Just remove the whole constructor.


for ( typename std::vector<std_cxx11::shared_ptr<Output::Interface<dim> > >::const_iterator itr= output.begin(); itr != output.end(); itr++)
{
output_suffixes.push_back((itr->get()->output_particle_data(particles,
Copy link
Contributor

Choose a reason for hiding this comment

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

there is an unnecessary pair of parentheses after push_back

output->save(os);

for ( typename std::vector<std_cxx11::shared_ptr<Output::Interface<dim> > >::const_iterator itr= output.begin(); itr != output.end(); itr++)
itr->get()->save(os);
Copy link
Contributor

Choose a reason for hiding this comment

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

this, and the corresponding load function only works if the list of output formats is exactly the same in the original run as in a restarted run. This is too brittle. Please copy the corresponding code from postprocess/interface.cc.

// a null pointer. Only initialize output if it was created.
if (output)
// Remove duplicate output formats specified in the parameter file.
auto last = std::unique(output_format_names.begin(), output_format_names.end());
Copy link
Contributor

Choose a reason for hiding this comment

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

we can't do C++11 yet. You need to provide the correct type for last.

if (output_format_names.size() != 1 || output_format_names[0] != "none")
for (std::vector<std::string>::const_iterator itr = output_format_names.begin();
itr != output_format_names.end(); itr++)
output.push_back(std_cxx11::shared_ptr<Output::Interface<dim>>
Copy link
Contributor

Choose a reason for hiding this comment

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

You need a space here, > >.

@hlokavarapu
Copy link
Contributor Author

hlokavarapu commented May 17, 2017

I have resolved all of your comments and I believe that this PR is ready to be reviewed one more time. However, I will continue to develop past the hackathon and am perfectly alright to delay the review process till next week. However given my track record. this might very well be the next hackathon. 😄

@hlokavarapu
Copy link
Contributor Author

I have found a bug related to resuming snapshots. This is WIP.

@hlokavarapu hlokavarapu changed the title Specifying multiple particle output formats [WIP] Specifying multiple particle output formats May 17, 2017
@hlokavarapu hlokavarapu changed the title [WIP] Specifying multiple particle output formats Specifying multiple particle output formats May 17, 2017
@hlokavarapu
Copy link
Contributor Author

Found and fixed the bug, so long as the tester is happy, I think this PR is ready to be reviewed.

Copy link
Member

@gassmoeller gassmoeller left a comment

Choose a reason for hiding this comment

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

Looks generally ok. There are a few pitfalls in the implementation, please take a look at my comments. Nothing looks impossible to resolve, but we need to discuss that a bit.

@@ -81,31 +75,27 @@ namespace aspect
const Property::ParticlePropertyInformation &property_information,
const double current_time);


Copy link
Member

Choose a reason for hiding this comment

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

Please remove this empty line

* Save the state of the object.
*/
* Save the state of the object.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Please revert this comment change, I think the old version was our usual convection.

@@ -37,16 +37,10 @@ namespace aspect
* @ingroup ParticleOutput
*/
template <int dim>
class ASCIIOutput : public Interface<dim>,
public SimulatorAccess<dim>
class ASCIIOutput : public Interface<dim>
Copy link
Member

Choose a reason for hiding this comment

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

Please remove the inclusion of simulator_access.h at the beginning of the header file, if you no longer (directly) derive this class from SimulatorAccess. Same for all other plugins that you modified in this PR.

@@ -40,10 +40,17 @@ namespace aspect
* @ingroup ParticleOutput
*/
template <int dim>
class Interface
class Interface : public SimulatorAccess<dim>
Copy link
Member

Choose a reason for hiding this comment

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

You need to add simulator_access.h in the list of includes at the top of this file. It currently seems to work without, because one of the other includes includes that one implicitly, but we can not rely on that fact.

*/
Interface ()
:
file_index (0) {}
Copy link
Member

Choose a reason for hiding this comment

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

Please move the definition of this constructor into the .cc file.

const std::string output_directory = output[0].get()->get_particle_output_location();
const std::string file_index = output[0].get()->get_file_index();

for ( typename std::vector<std_cxx11::shared_ptr<Output::Interface<dim> > >::const_iterator itr= output.begin(); itr != output.end(); itr++)
Copy link
Member

Choose a reason for hiding this comment

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

use ++itr unless there is need for itr++ (almost never, unless you modify the container within the loop). If you are interested in the topic here is a somewhat lengthy discussion: https://stackoverflow.com/questions/484462/difference-between-i-and-i-in-a-loop?noredirect=1&lq=1).

Copy link
Member

Choose a reason for hiding this comment

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

Also, we usually give iterators explanatory names, in this case output_object comes to mind. While it is longer, it makes the following lines easier to understand.

output_format_names.end());

// Create an output object depending on what the parameters specify so long as none has not been specified.
// If none and more than one other format is specified, then we ignore none.
Copy link
Member

Choose a reason for hiding this comment

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

Do you mean: "If none and one or more other format is specified, ..." ?

ExcMessage ("If you specify 'none' for the output format for parameter \"Data output format\","
"then this needs to be the only value given."));

if (output_format_names.size() != 1 || output_format_names[0] != "none")
Copy link
Member

Choose a reason for hiding this comment

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

After the AssertThrow above we are sure that if "none" was selected the output_format_names vector has size 1. So you could add the following line:

if (output_format_names[0] == "none")
  output_format_names.clear();

This would allow you to simplify the logic in the current line to if (output_format_names.size() > 0) and include the other loop below into this if-loop. I feel that would be easier to understand.


if (output_format_names.size() != 1 || output_format_names[0] != "none")
for (std::vector<std::string>::const_iterator itr = output_format_names.begin();
itr != output_format_names.end(); itr++)
Copy link
Member

Choose a reason for hiding this comment

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

Again please rename the iterators in the two for-loops. output_format and output_object come to mind.


return filename;
const std::string output_directory = output[0].get()->get_particle_output_location();
const std::string file_index = output[0].get()->get_file_index();
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 assume that file_index is the same for all output plugins. However, inside of the serialization of the plugins you seem to allow that they can be enabled or disabled during restarts (leading to different file indices in the different plugins). Would this lead to problems? At least it would cause the screen output and statistics output of the file names to be not consistent with the actual filenames of some plugins. Is there an easy fix for that? If not, how can we best document this behavior?

@bangerth
Copy link
Contributor

bangerth commented Aug 4, 2017

Let's close this given that #1869 is around now. I think re-implementing things in that framework would be a more useful approach, as discussed there.

@bangerth bangerth closed this Aug 4, 2017
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.

None yet

3 participants