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

'Simulation' type for managing time stepping #447

Closed
glwagner opened this issue Oct 8, 2019 · 6 comments · Fixed by #621
Closed

'Simulation' type for managing time stepping #447

glwagner opened this issue Oct 8, 2019 · 6 comments · Fixed by #621
Labels
abstractions 🎨 Whatever that means feature 🌟 Something new and shiny

Comments

@glwagner
Copy link
Member

glwagner commented Oct 8, 2019

It could be nice to have a type for managing time stepping --- eg, Simulation --- rather than requiring the writing of explicit loops as in

https://github.com/climate-machine/Oceananigans.jl/blob/4b7e5bced1019b1a6804d3797cfe0ed41fda4a51/examples/ocean_wind_mixing_and_convection.jl#L190

Simulation could look like

struct Simulation
    model
    Δt
    simulation_stop_time
    wall_time_limit
    simulation_stop_iteration
    progress
end

Or something along those lines. The field progress could either be a function or callable object, or tuple / list of functions or callable objects. The field Δt could either be a constant time-step or a TimeStepWizard.

We might also need a new type called ProgressMessage for managing logging / emitting progress messages for simulations. Then we can give it a frequency (and interval) of emission and support some other nice behaviors like a default format and auto-emission of diagnostic / monitoring results.

xref: #432 #431

@glwagner glwagner added the feature 🌟 Something new and shiny label Oct 8, 2019
@ali-ramadhan
Copy link
Member

I think it would be great to have these features so you can time step a model with fancy bells and whistles without an explicit loop.

I have mixed feelings about the Simulation type because it will add an extra layer on top of Model that most users will have to deal with. I think we can probably integrate all of this functionality into the existing Model struct.

Not sure what to call it, maybe model.time_step_manager or model.simulation_manager that looks like

struct SimulationManager
    Δt :: Union{Number, TimeStepWizard}
    simulation_stop_time
    wall_time_limit
    simulation_stop_iteration
    progress
end

@glwagner
Copy link
Member Author

I think modular is better and that Simulation is conceptually distinct from Model; that's why I proposed an extra type. Model is already extremely complex.

If you want, you can write a special constructor that builds both a Model and a Simulation. I think this is a better approach for providing ease-of-use than combining the Model and Simulation struct into a giant object with many unrelated fields.

@glwagner
Copy link
Member Author

I think you instantiate a model to do many things; one of them is time-stepping, but another may be analysis. Another is testing. Even now we can't actually solve poisson's equation without a Model easily, which seems like an unnecessary restriction of our non-modularity. I don't think we should make this problem worse.

I think it makes sense model is a field of Simulation. Its simple to envision Simulation parameters that are identical, with a different underlying model (eg, changing the number of passive tracers)`.

The way I envision a logical course for the development of complex software in general is that we start with the fundamental building blocks and make them as easy to use in their low-level form as possible. When we've decided we can proceed no farther, we add another layer of abstraction that fuses these underlying building blocks into a higher-order coherent object. And so on. I think its a better design strategy to add layers of abstraction, rather than embedding abstraction within already complex objects. The latter strategy would lead to unmanageable complexity.

@glwagner
Copy link
Member Author

glwagner commented Oct 15, 2019

Thinking about this more, I think it would make sense to make an even more radical change. I think we should add diagnostics, output_writers, and clock to Simulation.

The time_step! function then performs a single time-step, whereas to run a simulation one should write run!(simulation), which handles diagnostics, output writing, adaptive time-stepping, and logging in an integrated way.

This orthogonalizes the design a bit: diagnostics and output_writers are not really aspects of a "model", if we use a narrow interpretation of a model as a discrete representation of a physical system. A single physical system might conceivably be associated with a wide range of disparate diagnostics and output, depending on what kind of science is being done.

I think scripts become clearer. The user writes

model = Model(; model_parameters...)

simulation = Simulation(model; simulation_parameters...)

simulation.diagnostics[:diag] = # something

run!(simulation)

As an example to illustrate why Simulation is orthogonal to Model, here's a possible clear and coherent usage of this separation:

model = Model(; model_parameters...)

set!(model; first_interesting_initial_condition...)

first_simulation = Simulation(model, first_simulation_parameters...)
first_simulation[:diag] = diag_specific_to_first_simulation
run!(first_simulation)

set!(model; second_interesting_initial_condition...) # same physical model, but different starting initial condition... no new memory allocated, no recompilation --- fast

second_simulation = Simulation(model, second_simulation_parameters...)
second_simulation[:diag] = diag_specific_to_second_simulation
run!(second_simulation)

We can use run!(simulation, time_steps=nsteps) to allow hand-coded user loops that achieve a functionality similar to what we have now.

@ali-ramadhan
Copy link
Member

ali-ramadhan commented Feb 10, 2020

After last week's discussions I think I'm on board with the separate Simulation type.

I agree that output_writers and diagnostics can move outside of the Model and into Simulation as they're not used inside time_step!.

I think the clock should stay inside the model though. If we use a narrow interpretation of a model as a discrete representation of a physical system as you've suggested, then the current time is inherent to the physical system. Forcings and boundary conditions could be time-dependent, etc. In your second example, you should have probably set time (and iteration number) back to zero in addition to initializing all the prognostic fields.

I think this is what we have so far for a design:

struct Simulation
    model
    Δt
    diagnostics
    output_writers
    simulation_stop_time
    simulation_stop_iteration
    wall_time_limit
    progress
end

progress(model) will be a user-specified function that takes one input, the model.

Note: wall_time_limit will be useful for running long simulations on clusters with time limits (the majority of them).

@glwagner
Copy link
Member Author

Agree about clock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
abstractions 🎨 Whatever that means feature 🌟 Something new and shiny
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants