Source code for terrainbento.output_writers.static_interval_writer

#!/usr/bin/env python3

import itertools

from terrainbento.output_writers.generic_output_writer import (
    GenericOutputWriter,
)


[docs]class StaticIntervalOutputWriter(GenericOutputWriter): r"""Base class for new style output writers or converted old style output writers that want to use predetermined output intervals or times. The derived class defines what is actually produced. This base class handles when output occurs (and interfacing with the model loop via GenericOutputWriter). At minimum, derived classes must define **run_one_step** for generating the actual output. Calling **register_output_filepath** from the derived class allows for some optional file management features. See constructor and :py:class:`GenericOutputWriter` for more info. """
[docs] def __init__( self, model, name="static-interval-output-writer", intervals=None, intervals_repeat=True, times=None, **generic_writer_kwargs, ): """A class for generating output at predetermined intervals or times. Parameters ---------- model : a terrainbento ErosionModel instance name : string, optional The name of the output writer used when generating output filenames. Defaults to "static-interval-output-writer". intervals : float, int, list of floats, list of ints, optional A single float or int value indicates uniform intervals between output calls. A list of floats or ints indicates variable intervals between output times. Defaults to None which indicates that `times` will be used. If both `intervals` and `times` are None, will default to the producing one output at the end of the model run. intervals_repeat : bool, optional Indicates whether a list of intervals should repeat until the end of the model run. Only has effect if intervals is a list. Has no effect for scalar intervals (which always repeat) or if times is provided instead of intervals. Default is True. times : float, int, list of floats, list of ints, optional A single float or int value indicates only one output time. A list of floats or ints indicates multiple predetermined output times. Defaults to None which indicates that `intervals` will be used. If both `intervals` and `times` are None, will default to the one output at the end of the model run. The user must ensure that the times implied by `times_iter` align with the model timesteps used by the Clock. If a timestep is skipped a warning is raised and if more than five timesteps are skipped an error is raised. generic_writer_kwargs : keyword args, optional Keyword arguments that will be passed directly to GenericOutputWriter. These include: * add_id : bool, defaults to True * save_first_timestep : bool, defaults to False * save_last_timestep : bool, defaults to True * output_dir : string, defaults to './output' * verbose : bool, defaults to False Please see :py:class:`GenericOutputWriter` for more detail. Returns ------- StaticIntervalOutputWriter: object Examples -------- StaticIntervalOutputWriter is a base class that should not be run by itself. It contains the machinery for easily creating the output times iterator, but does not define **run_one_step**. Please see the terrainbento tutorial for output examples. """ super().__init__( model, name=name, **generic_writer_kwargs, ) self._intervals_repeat = intervals_repeat # Assert that intervals and times are not both provided. Not clear # which one should be used so crash the program. # Checks if at least one of the args is None assert intervals is None or times is None, "".join( [ "StaticIntervalOutputWriter does not accept both output ", "interval and output times simultaneously.", ] ) # Check if both args are None if intervals is None and times is None: # The caller provided neither intervals or times. Use the model end # time instead, meaning there will only be one output at # the end. times = model.clock.stop # Generate a iterator of output times either indirectly from the output # intervals or directly from output model times. if intervals is not None: times_iter = self._process_intervals_arg(intervals) self.register_times_iter(times_iter) elif times is not None: times_iter = self._process_times_arg(times) self.register_times_iter(times_iter)
# else: # Not a possible scenario. I use elif to be explicit with what that # section is for. def _process_intervals_arg(self, intervals): """Private method for processing the 'intervals' value provided to the constructor. Parameters ---------- intervals : float, int, list of floats, list of ints A single float or int value indicates uniform intervals between output calls. A list of floats or ints indicates variable intervals between output times. A list of intervals may be repeated if self._intervals_repeat is True. Returns ------- times_iter : An iterator of floats representing output times. """ times_iter = None if isinstance(intervals, (int, float)): # 'intervals' is a single integer or float number. assert intervals > 0, "Intervals must be positive number(s)" # Create a counting iterator that starts at 'intervals' and steps # by 'intervals'. Make sure the iterator produces floats start = float(intervals) step = float(intervals) times_iter = (start + step * i for i in itertools.count()) # times_iter = itertools.count(intervals, intervals) elif isinstance(intervals, list): if not all(isinstance(i, (int, float)) for i in intervals): # The list must contain only floats raise NotImplementedError( "".join( [ "Only floats or integers are currently supported for ", "the output interval list.", ] ) ) assert all( i > 0 for i in intervals ), "Intervals must be positive number(s)" if self._intervals_repeat: # The intervals list should repeat until the end of the model # run. Creates an infinite iterator. raw_iter = itertools.accumulate(itertools.cycle(intervals)) times_iter = (float(i) for i in raw_iter) else: # Create an iterator that steps through a list of output times # calculated by accumulating the list of output intervals. Make # sure they are floats. times_iter = ( float(i) for i in itertools.accumulate(intervals) ) else: raise NotImplementedError( f"Interval type {type(intervals)} not supported yet." ) assert times_iter is not None return times_iter def _process_times_arg(self, times): """Private method for processing the 'times' value provided to the constructor. Parameters ---------- times : float, int, list of floats, list of ints A single float or int value indicates only one output time. A list of floats or ints indicates multiple predetermined output times. Returns ------- times_iter : An iterator of float output times. """ times_iter = None if isinstance(times, (int, float)): # If a single integer or float is provided, create an iterator with # only one float value. times_iter = iter([float(times)]) elif isinstance(times, list): # Assert that it is a list of floats if not all(isinstance(i, (int, float)) for i in times): # The list must contain only floats raise NotImplementedError( "".join( [ "Only floats or integers are currently supported for ", "the output times list.", ] ) ) # Create an iterator that steps through the provided list of output # times. Make sure they are floats # times_iter = iter(times) times_iter = (float(i) for i in times) else: raise NotImplementedError( f"Output times type {type(times)} not supported yet." ) assert times_iter is not None return times_iter # Methods to override
[docs] def run_one_step(self): """The function which actually writes data to files (or screen).""" # This code is not needed here because it's in GenericOutputWriter... # But it's nice for explicitly showing that this function needs to be # defined by inheriting classes. raise NotImplementedError( "The inheriting class needs to implement this function" )