Created
December 2, 2015 00:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# #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. | |
""" |
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
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 followingrun_until
method,Just to be fair, the first loop from the example could be cleaned up and written more like the second,
(In Python 3.5,
xrange
can take a float step size so we could usefor 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 simplyrun
(or mayberun_for
if we go with a time step as an argument andrun_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
andupdate_until
instead ofrun
. Theupdate
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:
dt
orstop
argument, do we want to agree on a standard unit for time? seconds? days?