Class OutputManager

Inheritance Relationships

Derived Types

Class Documentation

class OutputManager

A base class to manage writing simulation outputs to disk.

The ascii and HDF5 output formats differ enough that almost every member function needs its own implementation for each, so this class defines only the interface and the behavior shared by both formats; the format-specific work is implemented by the OutputManagerAscii and OutputManagerH5 subclasses.

Subclassed by io::OutputManagerAscii, io::OutputManagerH5

Public Functions

explicit OutputManager(const SimControls &simControls)

Cache a reference to the simulation controls, and sanity-check the output-control flags.

simControls is stored by reference, so the object passed in must outlive this OutputManager. This base constructor does not open any output files; that is left to the constructors of the format-specific subclasses.

Unlike simControls itself, this class no longer needs the input deck at all: the output.write_* flags the subclass constructors gate their own group/file creation on are simControls’s own writeCluster()/writeClusterSpec()/ writeClusterPhot()/writeClusterYields()/ writeClusterFeedback()/writeGalaxy()/writeGalaxySpec()/ writeGalaxyPhot()/writeGalaxyYields()/writeGalaxyFeedback() (parsed by SimControls itself – see its own readOutput()), and the deck text a subclass constructor records for provenance is simControls’s own inputDeckStr() – so this constructor only sanity-checks the write_* flags’ own resulting combination:

Parameters:

simControls – Simulation controls (physics settings and control-flow settings together)

Throws:
  • std::runtime_error – if every output relevant to this simulation’s own SimType (all ten for a galaxy-type simulation; just the five writeCluster*() flags for a cluster-type simulation, since write_galaxy* is meaningless when there is no Galaxy object at all) is false, since nothing would be written

  • std::runtime_error – if writeClusterPhot() and writeGalaxyPhot() are both false while SimControls::filters()

    is non-null, since that means photometry was requested but would never be written anywhere – almost certainly a mistake, rather than a deliberate “compute filters() for

    some other purpose but discard the result” request

virtual ~OutputManager() = default
OutputManager(const OutputManager&) = delete
auto operator=(const OutputManager&) -> OutputManager& = delete
OutputManager(OutputManager&&) = delete
auto operator=(OutputManager&&) -> OutputManager& = delete
virtual void writeCluster(unsigned long trial, const core::Cluster &cluster) = 0

Write a cluster’s data as a row of the cluster output.

If cluster output was not enabled for this simulation, this is a no-op.

Parameters:
  • trial – Trial number to which this cluster belongs

  • cluster – The cluster whose data should be written

virtual void writeClusterSpec(unsigned long trial, double time, core::Cluster &cluster) = 0

Write a cluster’s spectrum as a row of the cluster-spectra datasets.

If spectral synthesis was not enabled for this simulation, or the cluster has disrupted, this is a no-op.

Parameters:
  • trial – Trial number to which this cluster belongs

  • time – The output time at which the cluster’s spectrum was computed, in yr

  • cluster – The cluster whose spectrum should be written; not const, since this may need to lazily compute the spectrum if it is not already current (see core::Cluster::spec()’s own comment)

virtual void writeClusterPhot(unsigned long trial, double time, core::Cluster &cluster) = 0

Write a cluster’s photometry as a row of the cluster-photometry datasets.

If no filter collection or bolometric luminosity was requested for this simulation, or the cluster has disrupted, this is a no-op.

Parameters:
  • trial – Trial number to which this cluster belongs

  • time – The output time at which the cluster’s photometry was computed, in yr

  • cluster – The cluster whose photometry should be written; not const – see writeClusterSpec()’s own comment

virtual void writeClusterYields(unsigned long trial, double time, core::Cluster &cluster) = 0

Write a cluster’s nucleosynthetic yields as a row of the cluster-yields datasets.

If no yield channels were requested for this simulation, this is a no-op. Unlike writeClusterSpec()/writeClusterPhot(), does not skip a disrupted cluster: core::Cluster::yields() keeps accumulating every one of the cluster’s own dead stars’ contributions regardless of disruption (see its own comment), so a disrupted cluster’s own row remains meaningful, and writing it does not double-count anything a galaxy-level yields total already includes.

Parameters:
  • trial – Trial number to which this cluster belongs

  • time – The output time at which this row was recorded, in yr

  • cluster – The cluster whose yields should be written; not const, matching writeClusterSpec()’s/writeClusterPhot()’s own signature, even though core::Cluster::yields() itself is never lazily (re)computed the way spec()/phot()/lbol() are (see its own comment)

virtual void writeGalaxy(unsigned long trial, double time, core::Galaxy &galaxy) = 0

Write a galaxy’s data as a row of the galaxy output.

If galaxy output was not enabled for this simulation (only possible for a galaxy-type simulation to begin with), this is a no-op. Otherwise, after writing this row, also calls writeCluster() on every currently-alive (non-disrupted) cluster in galaxy, so that each individual cluster is recorded in the clusters output too.

Parameters:
  • trial – Trial number to which this galaxy belongs

  • time – The output time at which this row was recorded, in yr

  • galaxy – The galaxy whose data should be written; not const, since this needs a non-const reference to loop over galaxy’s own clusters (see core::Galaxy::clusters()’s own comment)

virtual void writeGalaxySpec(unsigned long trial, double time, core::Galaxy &galaxy) = 0

Write a galaxy’s spectrum as a row of the galaxy-spectra datasets.

If spectral synthesis was not enabled for this simulation, this is a no-op. Otherwise, after writing this row, also calls writeClusterSpec() on every currently-alive (non-disrupted) cluster in galaxy.

Parameters:
  • trial – Trial number to which this galaxy belongs

  • time – The output time at which the galaxy’s spectrum was computed, in yr

  • galaxy – The galaxy whose spectrum should be written; not const – see writeGalaxy()’s own comment

virtual void writeGalaxyPhot(unsigned long trial, double time, core::Galaxy &galaxy) = 0

Write a galaxy’s photometry as a row of the galaxy-photometry datasets.

If no filter collection or bolometric luminosity was requested for this simulation, this is a no-op. Otherwise, after writing this row, also calls writeClusterPhot() on every currently-alive (non-disrupted) cluster in galaxy.

Parameters:
  • trial – Trial number to which this galaxy belongs

  • time – The output time at which the galaxy’s photometry was computed, in yr

  • galaxy – The galaxy whose photometry should be written; not const – see writeGalaxy()’s own comment

virtual void writeGalaxyYields(unsigned long trial, double time, core::Galaxy &galaxy) = 0

Write a galaxy’s nucleosynthetic yields as a row of the galaxy-yields datasets.

Writing the galaxy’s own row is a no-op if no yield channels were requested, or output.write_galaxy_yields is false – writeClusterYields() is still called on every currently-alive (non-disrupted) cluster in galaxy regardless, since output.write_cluster_yields is independently togglable (see SimControls::readYields()’s own sanity check, which only rejects both being false at once).

Parameters:
  • trial – Trial number to which this galaxy belongs

  • time – The output time at which this row was recorded, in yr

  • galaxy – The galaxy whose yields should be written; not const – see writeGalaxy()’s own comment

virtual void writeClusterFeedback(unsigned long trial, double time, core::Cluster &cluster) = 0

Write a cluster’s feedback (supernovae and stellar winds) as a row of the cluster-feedback datasets.

If cluster feedback output was not enabled for this simulation, this is a no-op. Like writeClusterYields(), and for the same reason, does not skip a disrupted cluster: its cumulative supernova count remains meaningful, and its stars still lose mass (core::Galaxy::mDotWind() likewise still counts every disrupted cluster’s own wind). The values written are those of feedbackRow().

Parameters:
  • trial – Trial number to which this cluster belongs

  • time – The output time at which this row was recorded, in yr

  • cluster – The cluster whose feedback should be written; not const, since core::Cluster::mDotWind()/pDotWind()/ eDotWind() are lazily (re)computed (see their own comment)

virtual void writeGalaxyFeedback(unsigned long trial, double time, core::Galaxy &galaxy) = 0

Write a galaxy’s feedback (supernovae and stellar winds) as a row of the galaxy-feedback datasets.

Writing the galaxy’s own row is a no-op if output.write_galaxy_feedback is false (or this is not a galaxy-type simulation) – writeClusterFeedback() is still called on every currently-alive (non-disrupted) cluster in galaxy regardless, since output.write_cluster_feedback is independently togglable, exactly as for writeGalaxyYields().

Parameters:
  • trial – Trial number to which this galaxy belongs

  • time – The output time at which this row was recorded, in yr

  • galaxy – The galaxy whose feedback should be written; not const – see writeGalaxy()’s own comment

virtual void checkpoint(unsigned long trialsCompleted) = 0

Roll over to a new checkpoint.

Closes whatever output is currently open – writing trialsCompleted as a top-level attribute on it first, so a later restart can tell how far the run had gotten as of this checkpoint – and opens a fresh one for the next checkpoint, so the current checkpoint’s output is fully flushed to disk before more trials are written to it – bounding how much work a crash or a walltime kill can lose, at the cost of one more output file to eventually consolidate. See OutputManagerH5::checkpoint() for the real implementation, and its own class header comment for the full checkpointing design.

Only meaningful with HDF5 output. OutputManagerAscii’s own implementation just throws (see its own comment): SimControls’s own constructor already rejects the combination of ascii output and a non-zero checkpointInterval() up front when parsing a real input deck, so this should never actually be reached from the CLI – but a caller building a SimControls directly (e.g. from Python) could set up that same illegal combination via setCheckpointInterval() without going through the constructor’s own check, so this still needs to fail safely if it is ever actually called.

Parameters:

trialsCompleted – Number of trials completed so far in the run, i.e. across every checkpoint including the one just closed, not just this checkpoint’s own share of them

virtual auto restartTrialsDone() const -> unsigned long = 0

Return the number of trials already completed by the run being restarted.

Only meaningful when this OutputManager was itself constructed to resume a previous, interrupted run; a caller that did not request that (see OutputManagerH5’s own constructor) should never call this.

Only meaningful with HDF5 output, for the same reason checkpoint() is: restarting requires a checkpoint to restart from, and only HDF5 output produces checkpoints at all. OutputManagerAscii’s own implementation just throws (see its own comment): main.cpp already rejects the combination of ascii output and a restart request before ever constructing SimCluster/SimGalaxy (and, transitively, before either of them could call this), so this should never actually be reached from the CLI – but, exactly as with checkpoint(), still needs to fail safely if it is ever actually called by a caller building these objects directly.

Returns:

The number of trials the run being restarted had already completed, as of the most recent checkpoint it left behind – see OutputManagerH5::restartSetup()’s own comment for how that is determined

virtual auto restartMaxTrial() const -> unsigned long = 0

Return the largest trial number the run being restarted ever actually wrote.

Only meaningful under the same conditions as restartTrialsDone() (only after a restart-constructed OutputManager, only with HDF5 output – OutputManagerAscii’s own implementation throws for the identical reason).

Deliberately distinct from restartTrialsDone(), even though both describe “how far did the run being restarted get”: under dynamic OpenMP scheduling, a batch that stopped partway through (a caught SIGTERM, or a per-trial exception) can finish with some higher-numbered trial done while a lower-numbered one is not (whichever thread happened to still be mid-flight when the others stopped taking on new work) – restartTrialsDone()

(a plain count of how many trials finished) stays accurate even then, but “trials

[0, restartTrialsDone()) are done” stops being a safe assumption once that can happen, so a restart cannot safely resume trial

numbering from restartTrialsDone() alone. It can always safely resume at restartMaxTrial() + 1, since that is defined as the largest number ever actually written – guaranteed never to collide with one already on disk – independently of restartTrialsDone() itself, which still correctly says how many more trials are needed to reach SimControls::nTrial(). SimCluster::run()’s/SimGalaxy::run()’s own comment has the full detail of how the two combine.

Returns:

The highest trial number the run being restarted had actually written output for, as of the most recent checkpoint it left behind – see OutputManagerH5::restartSetup()’s own comment for how that is determined

virtual void notifyEarlyTermination(unsigned long trialsCompleted) = 0

Record this rank’s own true final trials-completed count, for the destructor to use.

Despite the name (kept for the common, original case this was written for), this is called on every exit path from run(), not just an early one: OutputManagerH5’s own destructor otherwise assumes SimControls::nTrial() itself is this rank’s own final trial count on ordinary completion (see OutputManagerH5::closeOutputFile()’s own comment) – true in a single-rank run, where every rank is the whole run, but not under MPI, where nTrial() is the whole run’s target shared across every rank, not any one rank’s own share of it (see SimCluster::run()’s/SimGalaxy::run()’s own mpiPartitionRange() comment). Calling this unconditionally before returning, with this rank’s own true count either way, makes the destructor’s assumption correct in both cases rather than only the single-rank one – a no-op outside MPI, where the two values already agree whenever every trial actually completed.

Does not itself close or otherwise touch any output – OutputManagerH5’s own implementation just remembers trialsCompleted, for its destructor to write as the currently-open file’s own final “trials_completed” (and, where relevant, “restart_uid”) attribute instead of SimControls::nTrial(). Deliberately does not roll over to a new checkpoint the way checkpoint() does: on an early exit, there is nothing left to write into one, so doing so would just leave an empty, never-closed checkpoint behind for the destructor to eventually close with a wrong trial count of its own; on a normal exit, the current checkpoint is already the last one, so there is nothing to roll over to regardless.

OutputManagerAscii’s own implementation is a no-op, not a throw (unlike checkpoint()/restartTrialsDone()): ascii output has no trials_completed attribute (or any other summary of how many trials it holds) to correct in the first place, so there is nothing wrong to leave uncorrected – every row already written is already exactly as valid on an early exit as a normal one.

Parameters:

trialsCompleted – Number of trials this rank has actually completed – either short of SimControls::nTrial(), because a per-trial exception was thrown and rethrown out of SimCluster::run()/ SimGalaxy::run(), or because a SIGTERM was caught and handled gracefully (see their own comments); or, on a perfectly normal completion, this rank’s own final priorTrialsCompleted + trialsCompleted_ (see their own callers in SimCluster::run()/SimGalaxy::run())

Protected Attributes

const SimControls &simControls_

Simulation controls (physics and control-flow settings)

Protected Static Functions

static auto currentDateAndTime() -> std::pair<std::string, std::string>

Return the current local date (YYYY-MM-DD) and time (HH:MM:SS)

Returns:

A pair holding the date string followed by the time string

static auto currentRngStateString() -> std::string

Return the calling thread’s current rng state.

Returns:

The rng state, as a string suitable for writing to disk so a run can later be reproduced

static auto feedbackRow(core::Cluster &cluster) -> std::array<double, 4>

Return a cluster’s feedback quantities, in the units they are written to disk in.

core::Cluster reports its wind fluxes in cgs (g cm s^-2 and erg/s); this converts them to the more conventional units used in the output files. Shared by both output formats, so that they cannot disagree on units.

Parameters:

cluster – The cluster to read; not const – see writeClusterFeedback()’s own comment

Returns:

{ n_sn, mdot_wind, pdot_wind, edot_wind }: the cumulative number of supernovae (core::Cluster::cumSNe()), and the wind mass flux in Msun/yr, momentum flux in Msun km/s/yr, and energy flux in Lsun

static auto feedbackRow(core::Galaxy &galaxy) -> std::array<double, 4>

Return a galaxy’s feedback quantities, in the units they are written to disk in.

Parameters:

galaxy – The galaxy to read; not const – see writeGalaxy()’s own comment

Returns:

As for feedbackRow(core::Cluster&), from core::Galaxy::cumSNe()/mDotWind()/pDotWind()/eDotWind()