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

Support integer time #145

Open
5 of 14 tasks
Sbozzolo opened this issue Dec 17, 2024 · 1 comment
Open
5 of 14 tasks

Support integer time #145

Sbozzolo opened this issue Dec 17, 2024 · 1 comment

Comments

@Sbozzolo
Copy link
Member

Sbozzolo commented Dec 17, 2024

Draft. This is in progress

Tasks

Preview Give feedback
@ph-kev
Copy link
Member

ph-kev commented Jan 10, 2025

Problem

ClimaAtmos uses the same floating point type for time as for the spatial discretization. As a result, due to floating point errors, time can become inaccurate and even stop incrementing (especially with Float32). Consider the expression Float32(16777216) + Float32(1) == Float32(16777216) which evaluates to true rather than false. In this example, the simulation clock would stop if dt is one second. Furthermore, there are round-off errors that can accumulate when adding many small floating point numbers. For example, when running the Agnesi linear hydrostatic mountain experiment (uniform) configuration in ClimaAtmos, the resulting time is 3h 59 m 51.0s despite t_end being set to 14400.0 seconds or four hours. With inaccurate time, we also get inaccurate dates, which affect functions that depend on dates (e.g., reading files). Finally, floating point equality is trickly, which leads to our diagnostics being saved one timestep after they should.

Requirements

  • Any solution must support accurate tracking of time, meaning with no floating point errors.
  • It should be possible to count up to large times (10000s of years)
  • It should be possible to convert the time to a number (representing seconds). Conversion to float is necessary because quantities in ClimaAtmos (e.g. tendencies) are computed using the value of time in seconds (think about multiplying a state increment with dtγ).
  • It would be good if we could also convert to dates. Currently, converting to dates requires constantly carrying around start_date and computing the current date with the offset in second. It would be more natural if this was directly handled by the time. Conversion to dates is necessary because some functions, like diagnostics or external forcings, use dates.
  • It should provide an avenue to use different calendars in addition to the Gregorian (e.g., using CFTime)

Proposed solution

Here we propose ITime. ITime is a struct with the fields counter, period, and epoch, representing the how many clock cycles of period period occurred since epoch.

  • A counter is an integer (e.g. Int32 or Int64) that can be positive or negative
  • A period can be thought as Dates.FixedPeriod, and an epoch is a DateTime. This is not required, and other times can be used as long as they provide a function to be converted to seconds (e.g., from CFTime).

When a number is needed, call float on the time. For times that are already float, this is a no-op. For ITimes, this will convert them to float.

When a date is needed, calling date convert the itime to the corresponding date.

Operations with ITime

ITimes can be thought as numbers with units. E.g., ITime(counter = 10, period = Second(1)) represents 10 seconds. Therefore, the only operations that make sense are the ones that are dimensionally meaninfgul. For example,

  • itime1 + itime2 is fine and returns an ITime
  • itime1 * integer is fine and returns an ITime
  • itime1 * itime2 is not fine because the it would produce a quantity with dimensions of period1 * period2, which is not something we are representing

So, when thinking about operations, you can expect all the operations that don't change units to work, and all the other ones to not work.

When two ITimes have different period, we pick the greatest common denominator that can represent them both. This keeps time accurate (except for overflow or underflow, which can be dealt with by increasing the period).

One has to be careful with the functions one and oneunit: one returns the number 1, oneunit returns the ITime(1).

Dealing with times that cannot be represented

What happens when we try to multiply an ITime with a float?

The current choice is to round the result to the nearest ITime representable with the given period. For example
0.3 * ITime(4, Second(1)) will be ITime(1, Second(1)) because 4 * 0.3 = 1.2 which round to 1. Multiplying floats with ITime leads to loss of precision

In our current software, there is only one place where this happens: in the timestepper, we evaluate the time at the various stages. However, this only matters for tendencies that explicitely depend on time, such as insolation or reading forcings. For those tendencies, loss of precision is not an issue.

We considered an alternative to this: support rational within ITime and rationalize floats. We tested this and decided against. It led to more complex code with little benefits.

Caveats

  • Switching to ITime means that tendencies that depend explicitly on time will be approximated to their value at the beginning or end of the timestep
  • At the moment, float(itime) returns a Float64, so some calculations that were using Float32 will have a slightly different result.

Affected packages

  • ClimaUtilities: Support for ITime in TimeVaryingInputs.
  • ClimaDiagnostics: Support for ITime in schedules and writing data (e.g. NetCDF writers).
  • ClimaTimeSteppers: Support for ITime in TimeSteppers.
  • ClimaAtmos: Replace time with ITime and apply float when necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants