Skip to content

Instantly share code, notes, and snippets.

@SiccarPoint
Created December 2, 2015 00:23
Show Gist options
  • Save SiccarPoint/1bd979e42bb48d6ad0ab to your computer and use it in GitHub Desktop.
Save SiccarPoint/1bd979e42bb48d6ad0ab to your computer and use it in GitHub Desktop.
This shows the different ways one could have a component "time" parameter work
# #1: As a "run until" style:
# approach seems fiddly and annoying to me, but has advantage of always being general:
time_elapsed = 0.
dt = 5. # or whatever
while elapsed_time < total_time:
new_time = elapsed_time + dt
if new_time > total_time:
new_time = total_time - elapsed_time
my_component.run_component(new_time, *args, **kwds)
elapsed_time = new_time
# if actual "clock time" matters to the component (e.g., time of day for solar rad), this is clearly trivial
# to implement
# #2: As a timestep style.
# very easy to implement, iff total_time%dt == 0; <0.5 the lines of the other way
loops = int(total_time//dt)
assert total_time%dt == 0. # don't actually need to do this check if you know ahead of time you know this is true
for i in xrange(loops):
my_component.run_component(dt, *args, **kwds)
# if you can't be totally sure the total_time is a multiple of your dt (...but you can always *make* it so...!),
# you can do this:
loops = int(total_time//dt)
final_time_interval = total_time%dt
for i in xrange(loops):
my_component.run_component(dt, *args, **kwds)
my.component.run_component(final_time_interval, *args, **kwds)
# If your component requires an internal clock time, you'll have to track it separately. It can either be tracked
# internally to the component (I might prefer this? - after all, discritized differential equation components have
# to do this in "run until" cases already anyway), or passed in as an argument, or that kind of component just
# has a different signture that takes total time.
# Individual storm tracking is really easy this way, as the storms are easy to envision as just a duration and
# intensity pair:
from landlab.components.uniform_precip.generate_uniform_precip import PrecipitationDistribution
precip = PrecipitationDistribution(input_file)
for (dt, intensity) in precip.yield_storm_interstorm_duration_intensity():
my_component.run_component(dt, intensity=intensity)
# The equivalent set up for a run_until signature would look much uglier, and be a lot less intuitive.
"""
So, the way I see it, the dt method is almost always cleaner to work with, and arguably more pythonic
(look at that lovely generator in the final example!) but has the disadvantages that:
1. You have to be thinking about what you want to do. With run_until styles, the way it runs is totally
uniform every time (but that boilerplate text is twice as long, will run a bit slower, is arguably
more opaque, and requires logic testing in every iteration of the loop).
2. Any component that needs to know actual clock time doesn't get it "for free" any more. It either has
(a) to be tracked and passed in separately by the driver, (b) have a fundamentally nonstandard
signature, or (c) track clock time on its own internally. This actually might be a false choice anyway,
as internal tracking must already be occurring to handle the conversion of elapsed_time into
time_of_day, and we would be doing the equivalent for mass flux methods based on e.g., von Neumann
stability conditions (expressed as intervals, not clock times) if we chose the other option.
"""
@mcflugen
Copy link

mcflugen commented Dec 2, 2015

I guess if you have a method that runs until a stop time, that means each component needs to know what the current time is. I think it's probably easier to have the driver keep track of time. If, later on, we decide to have components keep track of time themselves it would be easy to have the Component base class define a run until method that just calls the run for method the appropriate number of times.

The Component class could have the following run_until method,

class Component(object):
    def run_until(self, stop):
        for step_no in xrange((stop - self.now) // self.dt):
            self.run(self.now + step_no * self.dt)
        self.run(stop - (self.now + step_no * self.dt))

Just to be fair, the first loop from the example could be cleaned up and written more like the second,

for step in xrange((stop - start) // dt):
    my_component.run(start + step * dt)
my_component.run(stop - (start + step * dt))

(In Python 3.5, xrange can take a float step size so we could use for time in xrange(start, stop, dt). It's also easy enough to write our own iterator, if we want.)

In any case, I like the run for style rather than the run until style. It seems easier to implement and is more flexible.

The name run_component, to me, seems redundant though. I suggest simply run (or maybe run_for if we go with a time step as an argument and run_until if we go with a stop time as an argument.).

Some alternatives to run_component:

  • my_component.run(dt)
  • my_component.run(stop)
  • my_component.run_for(dt)
  • my_component.run_until(stop)

In BMI, we use update and update_until instead of run. The update method takes no arguments and updates a component by a single time step. This is the internal component time step. update_until updates until a specific time.

A few random questions:

  • For either the dt or stop argument, do we want to agree on a standard unit for time? seconds? days?
  • What about components that don't have a concept of "time"? For instance, steady state models.
  • What if a component can't run until the specified time? For whatever reason, maybe it can't take a time step smaller than some amount.

@SiccarPoint
Copy link
Author

NB: the "run_component" method is just a dummy. I wasn't intending to suggest standardisation of this! I take your point on BMI, but I still like retaining component-specific method names, e.g., erode, route_flow, etc because I think it's clearer what each component is doing. There could also be components with more than one run method (e.g., the storm generating component). These would rule out using "..._for", grammatically as well. We could always have standard named wrappers for the functions in addition as a compromise.

Random thoughts:

  • Unit standardisation remains problematic. I think we can't enforce this, because different components could have totally justified need to use different order of magnitude times. Good documentation will be key, and the information is also available through var_units. Hypothetically, the property could be used to check component inter-compatibility at instantiation.
  • Steady state models just don't take it, e.g., fr.route_flow() we have already. Seems uncontroversial to me.
  • It's up to each component to subdivide the "imposed" time step if it needs to internally, clearly. Can you give an example of a case with a minimum step size?? That would be very problematic, but I can't envision a case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment