This introduction addresses some of the key concepts of FaIR and how they are now implemented inside the model. For worked examples of FaIR and the energy balance model, take a look at the examples
.
FaIR produces global mean temperature projections from various forcers. Input datasets can be provided in terms of emissions, concentrations (for greenhouse gases), or effective radiative forcing. Different species can be input in different ways, if they are internally consistent and valid (e.g. if you want to provide CO2 and short-lived climate forcer emissions, with concentrations of non-CO2 greenhouse gases and prescribed forcing for volcanic and solar forcing, like the esm-hist runs of CMIP6 Earth System Models). You can provide any number of species from single-forcing runs to full climate assessments. In "full-climate" mode, the process diagram in FaIR is similar to process
.
Simplified outline of available processes in FaIR. Some processes have been omitted for clarity.
FaIR is now a primarily object-oriented interface. The main interaction with FaIR is through the FAIR
class instance:
from fair import FAIR
f = FAIR()
FAIR()
contains the attributes and methods required to run the model. The energy balance climate model can also be run in standalone mode with prescribed forcing. In the rest of this introduction, f
is taken to be a FAIR
instance.
Typically there are two stages to a FaIR run. The first is to set up the dimensionality of the problem (define time horizon, included species and how to implement them), and the second is to fill in the data and do the run.
Internally, variables in are book-kept with numpy
arrays that have up to five dimensions:
Not all variables include all dimensions, but they are always in this order.
Parallelisation allows more than one scenario
and one set of configs
to be run at the same time. time
is the only axis which is looped over, allowing high efficiency multiple-scenario or multiple-configuration runs to be done on the same processor (subject to available RAM and disk space).
For inputs and outputs, we use xarray
, which wraps the numpy
arrays with axis labels and makes model input and output a little more tractable.
Defining the dimensionality of the problem is the first step of setting up FaIR.
FaIR uses two time variables to keep track of state variables: timepoints
and timebounds
. Only emissions are defined on timepoints
, where everything else is on timebounds
. As the name suggests, timebounds
are on the boundary of model time steps, and timepoints
are nominally at the centre of the model time step. timestepping
illustrates this.
Timebounds (red) and timepoints (blue) in FaIR, showing associated state variables defined on each time variable. FaIR can be run with prescibed emissions (blue arrows) and/or with prescribed concentrations for greenhouse gases (green arrows).
Time is specified in years, but you are not forced to use integer years. This can be useful for coupling directly with integrated assessment model derived emissions (often 5- or 10-year timesteps) or assessing short-term responses to volcanic forcing, where sub-annual responses can be important. timestepping_qtr
shows the timestepping for a 1/4-year timestep.
Timebounds (red) and timepoints (blue) in FaIR using a quarter-year time step.
A FaIR ecosystem therefore contains n/δt timepoints
and n/δt + 1 timebounds
where δt is the timestep and n is the number of years.
Time is defined as so:
f.define_time(start, end, timestep)
You can label time how you wish. Common runs for climate projections will start with a pre-industrial reference year (maybe 1750 or 1850), and run to 2100 or beyond:
f.define_time(1750, 2100, 1)
Or you might want to run an idealised pulse experiment, in which case it's less informative to assign real years and it is better to start at zero:
f.define_time(0, 1000, 1)
The simple non-integer example from timestepping_qtr
would thus be:
f.define_time(0, 3, 0.25)
We adopt the pandas
practice of including the end point.
For intepreting real dates, the timebound
1750 refers to 00:00 on 1 January 1750, and the timepoint
1750.5 is a mid-year average of the period 00:00 on 1 January 1750 to 00:00 on 1 January 1751. FaIR makes no adjustments for leap years or month length, and assumes each year is 365.24219 days long. The temporal difference between timebound
and timepoint
can be important when interpreting and reporting results: does temperature in 2100 mean at the timebound
2100, or do we want a mid-year average (corresponding to timepoint
2100.5, in which case we might want to interpolate or take an average of the 2100 and 2101 timebounds
.)
A scenario
is a set of emissions/concentration/forcing inputs and climate responses. Multiple scenarios
can be run in parallel. They are defined as a list of names, for example:
f.define_scenarios(['ssp126', 'ssp245', 'ssp370'])
Note at this stage we are only defining names: no data is being input into FaIR.
A config
defines a set of climate and species parameters to run FaIR with. For example, we might want to run with emulations of a few CMIP6 models, which have different climate sensitivities, aerosol forcing sensitivities to precurors, carbon cycle feedback strengths, and so on:
f.define_configs(['UKESM', 'NorESM', 'GFDL', 'MIROC'])
Again, we are only defining names at this stage.
Combined with the three scenarios
above, we have a matrix of 12 runs: each of three emissions scenarios will be run with each of four climate configs.
A specie
is anything that forces climate or other species in FaIR, which is a broad definition. This includes greenhouse gases, aerosol precurors, ozone precursors as expected, but also forcing categories (aerosol-radiation interactions, ozone forcing, land-use forcing) that may be calculated from other species.
Each specie
has an associated dict of properties
which defines how it is implemented in the particular run of FaIR and how it behaves. A properties
dict for a CO2 run might look like:
properties = {
'CO2': {
'type': 'co2',
'input_mode': 'emissions',
'greenhouse_gas': True,
'aerosol_chemistry_from_emissions': False,
'aerosol_chemistry_from_concentration': False,
}
}
The five dict keys of type
, input_mode
, greenhouse_gas
, aerosol_chemistry_from_emissions
and aerosol_chemistry_from_concentration
are all required. Both type
and input_mode
are from pre-defined lists. The API reference for fair.FAIR.define_species explains more.
Species are then declared in FaIR with:
f.define_species(['CO2'], properties)
Basic checks are performed to ensure that input specifications make sense (e.g. you cannot run a type solar
in emissions
mode, and many species types must be unique). An error will be raised if an invalid combination of options is provided.
For all but single-forcing experiments, defining species
this way could be quite onerous. To grab an emissions-driven run using all species
known to FaIR with their default values, use:
species, properties = read_properties()
f.define_species(species, properties)
FaIR uses a multiple-box atmospheric decay model with lifetime scaling for greenhouse gases (see e.g. [Millar2017]). This is represented by the box
dimension. By default, there are 4 boxes, but this can be modified in the initialisation:
f = FAIR(n_gasboxes=3)
or by accessing the attribute directly after initialisation:
f.n_gasboxes=3
layer
refers to the ocean layer of the energy balance model. By default, FaIR uses 3 layers, though this can be modified in the initialisation of the class:
f = FAIR(n_layers=2)
or by accessing the attribute directly:
f.n_layers=3
State variables are attributes of the FAIR
class. All state variables are outputs, and many are valid inputs (particularly for the first timebound
in which many must be provided with an initial condition).
After problem setup (see ref:Dimensionality), these xarrays
will be created inside FaIR with:
f.allocate()
Dimensions with invalid combinations are retained in the output (e.g. f.emissions
will be np.nan
for solar forcing) to maintain alignment between datasets.
Emissions are the only variable defined on timepoints
, and are quantified as an emissions rate per year. FaIR will automatically adjust the emissions flows if a non-annual timestep is provided. Emissions are input or output as the emissions
attribute of FAIR
:
f.emissions :: [timepoint, scenario, config, specie]
For greenhouse gases, concentrations can be input, or calculated:
f.concentration :: [timebound, scenario, config, specie]
Note the FaIR variable name is concentration
, in the singular.
When running in emissions mode, the initial concentration of a greenhouse gas should be specified (at timebound
0). We provide a convenience function, initialise()
, for specifying initial conditions:
from fair import initialise
initialise(f.concentration, 278.3, specie='CO2')
You could also directly modify the f.concentration
xarray
by label:
f.concentration.loc[dict(timebound=1750, specie='CO2')] = 278.3
or position, if you know that CO2 is index 0 of the specie axis:
f.concentration[0, :, :, 0] = 278.3
At the per-species level, effective radiative forcing can be input or calculated:
f.forcing :: [timebound, scenario, config, specie]
We use the shorter name forcing
, which should be taken to represent effective radiative forcing.
For some species like solar and volcanic, specifying forcing is the only valid input mode.
Again, initial forcing must be provided. In many cases, this will be zero for every species, in which case we don't have to specify it for each specie
:
initialise(f.forcing, 0)
The total effective radiative forcing is an output only:
f.forcing_sum :: [timebound, scenario, config]
and is simply forcing
summed over the specie
axis.
Units are W m-2.
Temperatures in FaIR are expressed as anomalies (in units of Kelvin) relative to some reference state, usually pre-industrial. Temperature is calculated from an n-layer ocean energy balance model:
f.temperature :: [timebound, scenario, config, layer]
The temperature near the surface and hence of most importance is layer 0. Again, initial conditions of temperature in all layers should be provided, and for a "cold-start" model can be done with:
initialise(f.temperature, 0)
One of the most important sources of climate projection uncertainty is the temperature response to forcing, which is governed by the climate_config
parameters (see later). These are varied across the config
dimension in FaIR. Simply, these define how much, and how quickly, temperature change occurs in response to a given forcing and encapsulates ECS
and TCR
which are emergent parameters.
Airborne emissions are the total stock of a specie
present in the atmosphere and usually expressed as an anomaly relative to pre-industrial:
f.airborne_emissions :: [timebound, scenario, config, specie]
Again, airborne_emissions
should be initialised. For "warm-start" runs, airborne emissions may be non-zero, and this value has influence on the carbon and methane cycles in FaIR.
Airborne fraction is the fraction of airborne emissions remaining in the atmosphere:
f.airborne_fraction :: [timebound, scenario, config, specie]
It is simply f.airborne_emissions
divided by f.cumulative_emissions
. It does not need to be initialised.
The cumulative emissions are the summed emissions since pre-industrial:
f.cumulative_emissions :: [timebound, scenario, config, specie]
cumulative_emissions
needs to be initialised. It is used in the carbon cycle and land use forcing components of FaIR.
The ocean heat content change is the time integral of the top of atmosphere energy imbalance (after some unit conversion):
f.ocean_heat_content_change :: [timebound, scenario, config]
Units are J. Divide by 1021 to get the more common zettajoules (ZJ) unit. It does not need to be initialised.
If using stochastic internal variability, this is the stochastic component of the total effective radiative forcing (see [Cummins2020]). Its dimensionality is:
f.stochastic_forcing :: [timebound, scenario, config]
Units are W m-2. It does not need to be initialised.
Follows from the energy balance model:
f.toa_imbalance :: [timebound, scenario, config]
Units are W m-2. It does not need to be initialised.
The partition fraction is the only state variable where the timebound
is not carried, which would necessitate a 5-dimensional array and use up useful memory. So we have:
f.gasbox_fractions :: [scenario, config, specie, gasbox]
Once the state variables have been allocated, we need to fill our input variables with data. Any valid method for inputting data into xarray
can be used. Additionally, we have a second convenience function fill()
that can do this for us:
from fair import fill
fill(f.emissions, 38, specie="CO2", scenario="ssp370")
Browse the examples
for instances where the variables are filled in.
For each defined config
(in our earlier example, we used the names from four CMIP6 models), we can (and should!) vary the model response. This gives us our climate uncertainty. There are two types: climate_configs
and species_configs
. As with the state variables, they are attributes of the FAIR instance, and implemented as xarray
Datasets.
climate_configs
define the behaviour of the energy balance model and contain the following variables:
- ocean_heat_transfer
- ocean_heat_capacity
- deep_ocean_efficacy
- forcing_4co2
- stochastic_run
- sigma_eta
- sigma_xi
- gamma_autocorrelation
- seed
They can be filled in for example using:
fill(f.climate_configs["ocean_heat_capacity"], [2.92, 11.28, 73.25], config='UKESM')
The API reference for the energy_balance_model explains more.
species_configs
define the behaviour of individual species: most variables have a species
dimension, some also have a gasbox
dimension. It contains the following variables:
- tropospheric_adjustment
- forcing_efficacy
- forcing_temperature_feedback
- forcing_scale
- partition_fraction
- unperturbed_lifetime
- molecular_weight
- baseline_concentration
- iirf_0
- iirf_airborne
- iirf_uptake
- iirf_temperature
- baseline_emissions
- g0
- g1
- concentration_per_emission
- forcing_reference_concentration
- greenhouse_gas_radiative_efficiency
- contrails_radiative_efficiency
- erfari_radiative_efficiency
- h2o_stratospheric_factor
- lapsi_radiative_efficiency
- land_use_cumulative_emissions_to_forcing
- ozone_radiative_efficiency
- aci_scale
- aci_shape
- cl_atoms
- br_atoms
- fractional_release
- ch4_lifetime_chemical_sensitivity
- lifetime_temperature_sensitivity
There are a lot of things that can be modified here. If you want to use default values for your defined species
, you can use:
f.fill_species_configs()
To override a default (or to fill it in if you didn't use fill_species_configs()
), modify the xarray
directly or use fill()
:
fill(f.species_configs["aci_shape"], 0.0370, config='UKESM', specie='Sulfur')
As simple as:
f.run()
The outputs are stored in the xarray
attributes. For example, if you ran emissions-driven, you should (if everything worked) find concentrations in f.concentration
calculated for species that were declared as greenhouse gases. And vice versa: FaIR will automatically back-calculate emissions where greenhouse gas concentrations are provided, if you set up your run this way.
If you find unexpected NaNs in your outputs, it's likely that something wasn't initialised in the above steps. Take a look at the examples
for workflows that should reproduce. If you can't figure it out and think it should work, raise an issue.
- ECS
The equilibrium climate sensitivity (ECS) is the long-term equilibrium global mean surface temperature response to a doubling of pre-industrial atmospheric CO2 concentrations with all other forcers fixed at pre-industrial. It is a measure of long-term sensitivity of climate.
- TCR
The transient climate response (TCR) is the global mean surface temperature change at the point where double pre-industrial atmospheric CO2 concentration is reached in an experiment where CO2 concentration is increased at a compound rate of 1% per year and all other forcers fixed at pre-industrial. This is around 69.7 years (many models use 70 years). It is a measure of both long-term sensitivity of climate and medium-term rate of climate response.