Skip to contents

The Hector model can be run subject to constraints that force the model to have a certain behavior. Technically, this means that the model’s components output user-provided data as opposed to their own calculations, similar to the data mode of a CESM sub-model. Currently, the available constraints include:

  • GHG concentrations
  • Radiative forcing
  • Temperature
  • Net Biome Production (NBP)

Constraints can be applied to Hector within the ini file (command-line & R) or using the Hector package setvar() function.

Setting Constraints in Hector

There are two ways to activate a constraint in Hector: through an ini file or with the setvar() function in R. Here is a generic overview of the two different approaches. See below for a more specific implementation of constraints.

Constraints via the ini file

A Hector input file is broken up into sections such as [core], [temperature], [carbon-cycle-solver], and so on. Within each section, users can define parameter values and pass in input variables – including constraints. All of the constraint variables are named with the pattern variable_constrain that points to the csv file where the variables’ time series are defined. When the line starts the symbol ;, the constraint is off and Hector is free running. When the constraint line beings without ;, then Hector is using the constraint and the user-provided data is used in lieu of Hector’s internal calculations. Changes to the constraints in the input files apply to Hector runs executed at the command line or with the R interface.

Constraints via setvar()

With the Hector R package, the setvar() function can be used to set constraints for a Hector instance. See below for specific examples or run help("constraints") or help("haloconstrain") for the static R help documentation.

Discontinuous constraints

Hector does not perform any interpolation of constraints – constraints are applied only in the exact years that they are provided by the user, and all other years (including those immediately before or after constrained years) are treated as unconstrained (typically, emissions-driven). This means that constraints can abruptly force Hector’s climate system into a state that is inconsistent with the previous time step. Concentrations for the first year of a constraint can be discontinuous compared to the previous year without a constraint. Concentrations after the last year of a constraint will not necessarily have a discontinuity, but the behavior of the corresponding component and any related components may change after this point. It is the user’s responsibility to make sure that constraints are continuous.

Current Constraint Capabilities

Atmospheric CO2

CO\(_2\) concentrations ([CO\(_2\)], given in ppmv CO\(_2\)) have the largest effect on total radiative forcing and are affected by carbon-cycle climate interactions. Running Hector with the CO\(_2\) concentration constraint turned on mimics the default behavior of the Earth System Models which are GHG concentration driven. This capability is also helpful with debugging. With the atmospheric CO\(_2\) constraint turned on, carbon-cycle and climate feedback parameters (\(Q_{10}\) and \(\beta\)) are essentially ignored.

Uncommenting (removing the ;) from the following line in the [simpleNbox] section of the input file will turn the atmospheric CO\(_2\) concentration constraint on.

;CO2_constrain=csv:path/to/constraint/file.csv

Then the model’s atmospheric CO\(_2\) concentration ([CO\(_2\)], given in ppmv CO\(_2\)) will follow the contents of this file (which must include at least two columns titled Date and CO2_constrain). Alternatively, Hector’s atmospheric CO\(_2\) concentration constraint can be set using the setvar() function and CO2_CONSTRAIN().

Here is an example in R.

# Set up an instance of Hector & run, this is the free running Hector to be
# used as a comparison to the constrained Hector
ini <- system.file("input/hector_ssp245.ini", package = "hector")
core <- newcore(ini, name = "ssp245")
invisible(run(core)) # `invisible` prevents Hector core info from being printed out

# Extract the output
dates_to_keep <- 1850:2100
vars_to_keep <- c(ATMOSPHERIC_CO2(), RF_TOTAL(), GLOBAL_TAS())
free_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

# Make a time series of new CO2 values to use as the constraint
constraint_yrs <- 1950:2050
new_co2 <- seq(from  = 200, to = 400, length.out = length(constraint_yrs))

# Use setvar() to set up the the constraint, reset and run Hector, and fetch the results
setvar(core, dates = constraint_yrs, var = CO2_CONSTRAIN(), values = new_co2,
       unit = getunits(CO2_CONSTRAIN()))
reset(core)
## Hector core: ssp245
## Start date:  1745
## End date:    2300
## Current date:    1745
## Input file:  /home/runner/work/_temp/Library/hector/input/hector_ssp245.ini
invisible(run(core))
constrained_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

Plot results from the free running Hector with the constrained Hector.

How the constraint is implemented in Hector (C++)

The CO\(_2\) constraint is applied at the end of the current time step. The full sequence of events is as follows: First, the model solves the current time-step’s carbon cycle conditioned on the previous time step’s carbon pools, ignoring the CO\(_2\) constraint. Then, if a CO\(_2\) constraint is present, Hector calculates the difference between its calculated atmospheric CO\(_2\) and the prescribed CO\(_2\) constraint. If the target atmospheric CO\(_2\) concentration is lower than the calculated CO\(_2\), the excess carbon is transferred from the atmosphere to the deep ocean; conversely, if the target atmospheric CO\(_2\) concentration is greater than the calculated value, the additional carbon is transferred from the deep ocean into the atmosphere. Finally, Hector records the current atmospheric CO\(_2\) concentration (which is now equal to the constraint value); this is the value that is used in the next time step’s evaluation of carbon-climate feedbacks (e.g. CO\(_2\) fertilization of net primary productivity, surface ocean carbonate chemistry…). In other words, an atmospheric CO\(_2\) constraint at time \(t\) does not affect carbon-climate feedbacks until \(t+1\).

The CO\(_2\) constraint need not span the entirety of the Hector simulation, or even be continuous. At any given time step, Hector will check whether or not a CO\(_2\) constraint exists at that time step and only apply the constraint if it is present. This means that “hybrid” runs are possible, where only some specific time ranges have CO\(_2\) constraints while others calculate the atmospheric carbon pool and CO\(_2\) concentrations according to Hector’s standard carbon cycle.

Non CO2 concentrations

Hector can constrain non CO\(_2\) GHG concentrations (N\(_2\)O, CH\(_4\), and halocarbons). These all work in more-or-less the same way as the atmospheric CO\(_2\) constraint (above), except that there is no equivalent to the carbon pool adjustment for these gases.

In the input file, they are triggered by a line like the following in their corresponding component section:

X_constrain=csv:path/to/file.csv

As with other CSV inputs, the /path/to/file.csv above must include columns for Date and X_constrain.

Alternatively, constraints can be set from the R interface using setvar(). Implementing these constraints follows the same pattern as the temperature, radiative forcing, and atmospheric CO\(_2\): Set up an active Hector core, use setvar() to set the constraint, run, and fetch the output from the Hector core.

How the constraint is implemented in Hector (C++) using CH4 as an example

At any given time step, Hector will check if the constraint has a value for that time step (e.g. CH4_constrain.exists(t)). If the constraint exists, Hector will skip all atmospheric chemistry calculations and set the corresponding atmospheric concentration value to the constraint value (e.g. CH4.set(t, CH4_constrain.get(t)). If the constraint does not exist, Hector will proceed with its standard atmospheric chemistry routines for calculating the atmospheric concentration based on emissions and concentrations of other relevant species. This means that concentration constraints always take precedence over emissions; in other words, if a particular time step has both an emissions and a concentration constraint, the emissions constraint is ignored.

Note that CH\(_4\) and N\(_2\)O have special variables for their pre-industrial concentration that could conflict with the constraint. In Hector, concentration constraints take precedence over pre-industrial concentrations; in other words, Hector assumes that if you provide a concentration constraint for the pre-industrial timestep, that is the concentration you want, regardless of the prescribed pre-industrial value. The equations for CH\(_4\) and N\(_2\)O concentrations to radiative forcing were parameterized with specific pre-industrial concentrations. Users must be cautions of changes to pre-industrial values and how it may affect the radiative forcing calculations.

Radiative forcing

The model’s global radiative forcing Ftot which drives global temperature changes can be constrained. Free running Hector calculates Ftot based on the radiative forcing contributions from GHG concentrations, aerosol emissions, and forcing inputs [UNSURE WORDING] (e.g. land albedo and solar forcing). Using the radiative forcing constraint Ftot_constrain means any parameters and/or variables that affect radiative forcing (such as carbon cycle feedback parameters or emissions) will not affect Ftot while the constraint is in effect. The model will extrapolate within the Ftot_constrain as necessary, but not beyond its endpoints. Once Hector’s internal date passes the last date for Ftot_constrain, the model’s forcing component becomes unconstrained.

Uncommenting (removing the ;) from the following line in the [forcing] section of the input file will turn the radiative forcing constraint on.

;Ftot_constrain=csv:path/to/constraint/file.csv

Here is an example of using the R package to implement the radiative forcing constraint.

# Set up an instance of Hector & run
ini <- system.file("input/hector_ssp245.ini", package = "hector")
core <- newcore(ini, name = "ssp245")
invisible(run(core)) # the invisible call prevents Hector core info from being printed out

# Extract the output.
dates_to_keep <- 1850:2100
vars_to_keep <- c(RF_TOTAL(), GLOBAL_TAS())
free_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

# Make a time series of new RF values to use as the RF constraint. We are only
# going to change the RF for a subset of the years.
new_rf <- free_out[free_out$variable == RF_TOTAL() & free_out$year %in% 1950:2050, ]
new_rf$value <- new_rf$value + 3 # increase the total RF

# Use setvar() to set up the the constraint, reset and run Hector, and fetch the results.
setvar(core, dates = new_rf$year, var = FTOT_CONSTRAIN(), values = new_rf$value, unit = "W/m2")
reset(core)
## Hector core: ssp245
## Start date:  1745
## End date:    2300
## Current date:    1745
## Input file:  /home/runner/work/_temp/Library/hector/input/hector_ssp245.ini
invisible(run(core))
constrained_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

Plot the total radiative forcing and global mean temperature. Recall the constraint is only in effect between 1950 and 2050.

Temperature

Uncommenting (removing the ;) from the following line in the [temperature] section of the input file will turn the global mean constraint on.

tas_constrain=csv:path/to/constraint/file.csv

Then the model’s global mean temperature global_tas will follow the contents of this file (which must contain columns titled Date and tas_constrain; a sample file is included in the repository (see inst/input/tables/tgav_historical.csv). The model will extrapolate within this data as necessary, but not beyond its endpoints.

Once Hector’s internal date passes the last date in the constraint file, the model’s temperature component becomes unconstrained, except that we do not permit it to suddenly jump to a new temperature. Instead, changes in global radiative forcing (from which temperature change is computed) are applied to the end-of-constraint temperature. For more details, see the detailed comments in the TemperatureComponent::run (temperature_component.cpp).

Here is an example of using the R package to implement the global average mean temperature (global_tas) constraint.

# Set up an instance of Hector & run
ini <- system.file("input/hector_ssp245.ini", package = "hector")
core <- newcore(ini, name = "ssp245")
invisible(run(core)) # `invisible` prevents Hector core info from being printed

# Extract the output
dates_to_keep <- 1850:2100
vars_to_keep <- c(GLOBAL_TAS())
free_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

# Make a time series of new temperature values to use as the constraint
new_tgav <- free_out[free_out$variable == GLOBAL_TAS(), ]
new_tgav$value <- new_tgav$value + new_tgav$value * 0.25 # increase global temp by 25%

# Use setvar() to set up the the constraint, reset and run Hector, and fetch the results
setvar(core, dates = new_tgav$year, var = TAS_CONSTRAIN(), values = new_tgav$value,
       unit = getunits(TAS_CONSTRAIN()))
reset(core)
## Hector core: ssp245
## Start date:  1745
## End date:    2300
## Current date:    1745
## Input file:  /home/runner/work/_temp/Library/hector/input/hector_ssp245.ini
invisible(run(core))
constrained_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

Plot the total radiative forcing and global mean temperature. Recall the constraint is only in effect between 1950 and 2050.

NBP

The NBP constraint can be activated via the ini file by adding the line

NBP_constrain=csv:path/to/constraint/file.csv

to the [simpleNbox] section of the input file, where the constraint csv file has columns titled Date and NBP_constrain similar to the sample file is included in the repository (see inst/input/tables/tgav_historical.csv).

Here is an example implemented in the R interface

constraint_yrs <- 2001:2010
constraint_values <- c(0.1, 0.2, 0.3, -0.4, -0.5, -0.6, 0.7, 0.8, 0.9, 1.0)

ini <- system.file("input/hector_ssp245.ini", package = "hector")
core <- newcore(ini, name = "ssp245")
invisible(run(core)) # `invisible` prevents Hector core info from being printed out

# Extract the output
dates_to_keep <- 1850:2100
vars_to_keep <- c(ATMOSPHERIC_CO2(), NBP(), NPP(), RH())
free_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

# Use setvar() to set up the the constraint, reset and run Hector, and fetch the results
setvar(core, dates = constraint_yrs, var = NBP_CONSTRAIN(), values = constraint_values,
       unit = getunits(NBP_CONSTRAIN()))
reset(core)
## Hector core: ssp245
## Start date:  1745
## End date:    2300
## Current date:    1745
## Input file:  /home/runner/work/_temp/Library/hector/input/hector_ssp245.ini
invisible(run(core))
constrained_out <- fetchvars(core, 1850:2100, vars = vars_to_keep)

Plot results from the free running Hector with the constrained Hector.

The model hits the constraint exactly by raising RH and lowering NPP. Because of this, atmospheric CO\(_2\) rises rapidly during the constraint period, as we’ve basically shut down the land C sink. As a result, when the model exits the constraint period, NPP shoots up very high (because the atmosphere is saturated with CO\(_2\)). In a similar vein, the suppressed NPP means, once outside of the constraint, that RH is low and thus the land becomes a large sink as it draws down the excess atmospheric CO\(_2\).