Header menu logo bristlecone

A fully-worked example: soil nutrients and plant growth

The following code demonstrates the key functionality of Bristlecone, using a real-world example. Specifically, it demonstrates model-fitting and model-selection for the mechanisms controlling nutrient limitation to shrub individuals in the Arctic tundra.

The hypotheses are tested on a per-shrub basis. For each shrub, there are four components that may vary in the model, which when multiplied together results in 12 possible combinations of mechanisms for each shrub.

First, we must load Bristlecone.

open Bristlecone // Opens Bristlecone core library and estimation engine
open Bristlecone.Language // Open the language for writing Bristlecone models
open Bristlecone.Time

Defining a 'base model'

Then, we define a base model system, into which we can nest the components that vary (which represent different hypotheses).

Our base model consists of the following parts:

The code for the parts of the base model is shown below.

// Empirical transform from δ15N to N availability.
let ``δ15N -> N availability`` =
    (Constant 100. * Environment "N" + Constant 309.) / Constant 359.

// ODE1. Cumulative stem biomass
let ``db/dt`` geom nLimitation =
    Parameter "r" * (geom This) * This * (nLimitation ``δ15N -> N availability``)
    - Parameter "γ[b]" * This

// ODE2. Soil nitrogen availability
let ``dN/dt`` geom feedback limitationName nLimitation =
    if limitationName = "None" then
        Parameter "λ" - Parameter "γ[N]" * ``δ15N -> N availability`` + feedback This
    else
        Parameter "λ" - Parameter "γ[N]" * ``δ15N -> N availability`` + feedback This
        - (geom This) * This * (nLimitation ``δ15N -> N availability``)

// Measurement variable: stem radius
let stemRadius lastRadius lastEnv env =
    let oldCumulativeMass = lastEnv |> lookup "bs"
    let newCumulativeMass = env |> lookup "bs"

    if (newCumulativeMass - oldCumulativeMass) > 0. then
        newCumulativeMass |> Allometric.Proxies.toRadiusMM // NB Allometrics is given in full in the downloadable script.
    else
        lastRadius

Once we have defined the components, we can scaffold them into a model system. We can plug in the nestable hypotheses (defined further below) by defining the base model as a function that takes parameters representing the alternative hypotheses.

let ``base model`` geometricConstraint plantSoilFeedback (nLimitMode, nLimitation) =
    Model.empty
    |> Model.addEquation "bs" (``db/dt`` geometricConstraint nLimitation)
    |> Model.addEquation "N" (``dN/dt`` geometricConstraint plantSoilFeedback nLimitMode nLimitation)
    |> Model.includeMeasure "x" stemRadius
    |> Model.estimateParameter "λ" notNegative 0.001 0.500
    |> Model.estimateParameter "γ[N]" notNegative 0.001 0.200
    |> Model.estimateParameter "γ[b]" notNegative 0.001 0.200
    |> Model.useLikelihoodFunction (ModelLibrary.Likelihood.bivariateGaussian "x" "N")
    |> Model.estimateParameter "ρ" noConstraints -0.500 0.500
    |> Model.estimateParameter "σ[x]" notNegative 0.001 0.100
    |> Model.estimateParameter "σ[y]" notNegative 0.001 0.100

Defining the competing hypotheses

Here, we define 12 alternative hypotheses by defining three interchangeable components:

Once we have defined each of the three components, we can take the product of them with the base model which forms 12 alternative hypotheses, each represented as a ModelSystem.

Geometric constraint

Plants do not grow indefinitely, but eventually reach an asymptotic mass owing either to geometric or resource constraints. Here, we define two competing hypotheses: that a shrub does not show evidence of nearing its asymptote, or that it does (based on a Chapman-Richards growth function).

In Bristlecone, we use modelComponent to construct a pluggable component into a model system. We pass modelComponent a list of subComponents, which each have a name and an equation. In its equation, a model component can take one or many parameters, but these must match the signiture required by the hole in the base model. For example, the geometric constraint here takes the current mass only.

In addition, a subComponent may require additional estimatable parameters over the base model. In this case, the Chapman-Richards model requires an extra parameter K, which represents the asymptotic biomass. These may be added to a subComponent by using |> estimateParameter afterwards, as below.

let ``geometric constraint`` =
    modelComponent
        "Geometric constraint"
        [ subComponent "None" (Constant 1. |> (*))
          subComponent "Chapman-Richards" (fun mass -> Constant 1. - (mass / (Parameter "k" * Constant 1000.)))
          |> estimateParameter "k" notNegative 3.00 5.00 ] // Asymptotic biomass (in kilograms)

Plant-soil feedback

The plant-soil feedback is the flow of nutrients from plant biomass into the soil available nitrogen pool. Here, we effectively turn on or off N input into the soil pool on biomass loss. In the base model, density-dependent biomass loss occurs. Turning on the feedback here creates an N input into soil based on the biomass lost multiplied by a conversion factor ɑ.

let ``plant-soil feedback`` =

    let biomassLoss biomass =
        (Parameter "ɑ" / Constant 100.) * biomass * Parameter "γ[b]"

    modelComponent
        "Plant-Soil Feedback"
        [ subComponent "None" (Constant 1. |> (*))
          subComponent "Biomass Loss" biomassLoss
          |> estimateParameter "ɑ" notNegative 0.01 1.00 ] // N-recycling efficiency

Nitrogen limitation

We specify three plausable mechanisms for nutrient limitation to shrub growth: (1) that growth is independent on soil N availability; (2) that growth is dependent on soil N availability in a linear way; or (3) that a mechanistic model of root-foraging (saturating) best represents N-limitation of shrub growth.

A new concept here is the Conditional element in an equation. This term exposes a ModelExpression -> float (compute), allowing a calculation to be conditional on the state of parameters or values. In this example, we use it to restrict the models such that the N-limiting effect cannot be zero.

let ``N-limitation to growth`` =

    let saturating minimumNutrient nutrient =
        let hollingModel n =
            (Parameter "a" * n)
            / (Constant 1. + (Parameter "a" (** Parameter "b"*) * Parameter "h" * n))

        Conditional
        <| fun compute ->
            if compute (hollingModel minimumNutrient) < 1e-12 then
                Invalid
            else
                hollingModel nutrient

    let linear min resource =
        Conditional
        <| fun compute ->
            if compute (Parameter "a" * min) < 1e-12 then
                Invalid
            else
                Parameter "a" * resource

    modelComponent
        "N-limitation"
        [ subComponent "Saturating" (saturating (Constant 5.))
          |> estimateParameter "a" notNegative 0.100 0.400
          |> estimateParameter "h" notNegative 0.100 0.400
          |> estimateParameter "r" notNegative 0.500 1.000
          subComponent "Linear" (linear (Constant 5.))
          |> estimateParameter "a" notNegative 0.100 0.400
          |> estimateParameter "r" notNegative 0.500 1.000
          subComponent "None" (Constant 1. |> (*))
          |> estimateParameter "r" notNegative 0.500 1.000 ]

Putting the hypotheses together

let hypotheses =
    ``base model``
    |> Hypotheses.createFromComponent ``geometric constraint``
    |> Hypotheses.useAnother ``plant-soil feedback``
    |> Hypotheses.useAnotherWithName ``N-limitation to growth``
    |> Hypotheses.compile

The resultant hypotheses (each of which is a Hypotheses.Hypothesis) are:

Hypothesis 1 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + None + Saturating
Hypothesis 2 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + None + Saturating
Hypothesis 3 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + Biomass Loss + Saturating
Hypothesis 4 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + Biomass Loss + Saturating
Hypothesis 5 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + None + Linear
Hypothesis 6 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + None + Linear
Hypothesis 7 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + Biomass Loss + Linear
Hypothesis 8 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + Biomass Loss + Linear
Hypothesis 9 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + None + None
Hypothesis 10 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + None + None
Hypothesis 11 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: None + Biomass Loss + None
Hypothesis 12 [MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]_MICROSOFT.FSHARP.COLLECTIONS.IENUMERATOR+MKSEQ@177[SYSTEM.COLLECTIONS.GENERIC.IENUMERABLE`1[SYSTEM.CHAR]]]: Chapman-Richards + Biomass Loss + None

Setting up a Bristlecone engine

A bristlecone engine provides a fixed setup for estimating parameters from data. We use the same engine for all model fits within a single study.

Here, we scaffold an engine from Bristlecone.mkContinuous, as we are working with continuous-time models.

Note. In a non-docs environment, we may choose to use a logger for this example that outputs real-time traces in graphical format using ggplot in R. To do this, we can call withOutput with Logging.RealTimeTrace.graphWithConsole 60. 10000 to use the graphics functions from the Bristlecone.Charts.R package.

let output = Logging.Console.logger 1000

let engine =
    Bristlecone.mkContinuous
    |> Bristlecone.withContinuousTime Integration.MathNet.integrate
    |> Bristlecone.withOutput output
    |> Bristlecone.withConditioning Conditioning.RepeatFirstDataPoint
    |> Bristlecone.withCustomOptimisation (
        Optimisation.MonteCarlo.SimulatedAnnealing.fastSimulatedAnnealing
            0.01
            true
            { Optimisation.MonteCarlo.SimulatedAnnealing.AnnealSettings<float>.Default with
                InitialTemperature = 100.
                TemperatureCeiling = Some 100.
                HeatRamp = (fun t -> t + 5.00)
                BoilingAcceptanceRate = 0.85
                HeatStepLength = Optimisation.EndConditions.afterIteration 1000
                TuneLength = 1000
                AnnealStepLength =
                    (fun x n ->
                        Optimisation.MonteCarlo.SimulatedAnnealing.EndConditions.improvementCount 5000 250 x n
                        || Optimisation.EndConditions.afterIteration 10000 x n) }
    )

Testing the engine and model

Running a full test is strongly recommended. The test will demonstrate if the current configuration can find known parameters for a model. If this step fails, there is an issue with either your model, or the Bristlecone configuration.

let testSettings =
    Test.create
    |> Test.addNoise (Test.Noise.tryAddNormal "σ[y]" "N")
    |> Test.addNoise (Test.Noise.tryAddNormal "σ[x]" "bs")
    |> Test.addGenerationRules
        [ Test.GenerationRules.alwaysMoreThan -3. "N"
          Test.GenerationRules.alwaysLessThan 20. "N"
          Test.GenerationRules.alwaysMoreThan 0. "bs"
          Test.GenerationRules.monotonicallyIncreasing "x" ] // There must be at least 10mm of wood production
    |> Test.addStartValues [ "x", 5.0; "bs", 5.0<Dendro.mm> |> Allometric.Proxies.toBiomassMM; "N", 3.64 ]
    |> Test.withTimeSeriesLength 30
    |> Test.endWhen (Optimisation.EndConditions.afterIteration 1000)
let testResult =
    hypotheses
    |> List.map ((fun h -> h.Model) >> Bristlecone.testModel engine testSettings)

Load in the real dataset

Here, we are using the Bristlecone.Dendro package to read in dendroecological data. For other problems, any method to wrangle the data into a TimeSeries is acceptable.

open Bristlecone.Dendro

let dataset =
    let plants =
        Data.PlantIndividual.loadRingWidths (__SOURCE_DIRECTORY__ + "/../data/yamal-rw.csv")

    let isotopeData =
        Data.PlantIndividual.loadLocalEnvironmentVariable (__SOURCE_DIRECTORY__ + "/../data/yuribei-d15N-imputed.csv")

    plants |> PlantIndividual.zipEnvMany "N" isotopeData

Model-fitting to real data

In this step, we will apply our EstimationEngine to the real shrub data and model hypotheses.

Because there are so many hypotheses (x12) and individuals (x10), and we want to run replicate analyses (x3), it makes sense to run these 360 workloads in parallel. To do this, we can make use of the workflow tools in the Bristlecone.Workflow namespace.

An orchestration agent is an agent that queues and runs work items in parallel. Below, we demonstrate setting up an agent:

open Bristlecone.Workflow

let orchestrator =
    Orchestration.OrchestrationAgent(
        writeOut = output,
        maxSimultaneous = System.Environment.ProcessorCount,
        retainResults = false
    )

Before we can run the models, there are some specific considerations required for this problem.

Setting the start values

Given time-series data, a common modelling problem is the question of what to set t = 0 as. One strategy is to repeat the first data point (t1) as t0. In this instance, out isotope time-series are shorter than the ring-width time-series, so we generally know the real value of one time-series but not the other. Because of this, we use a custom start point for each shrub individual.

let startValues (startDate: System.DateTime) (plant: PlantIndividual.PlantIndividual) =
    let removeUnit (x: float<_>) = float x

    let initialRadius =
        match plant.Growth with
        | PlantIndividual.PlantGrowth.RingWidth s ->
            match s with
            | GrowthSeries.Cumulative c ->
                let trimmed = c |> TimeSeries.trimStart (startDate - System.TimeSpan.FromDays(366.))

                match trimmed with
                | Some t -> t.Values |> Seq.head
                | None -> failwith "Could not get t0 from ring-width series"
            | _ -> invalidOp "Not applicable"
        | _ -> invalidOp "Not applicable"

    let initialMass = initialRadius |> Allometric.Proxies.toBiomassMM
    let initialNitrogen = plant.Environment.[(code "N").Value].Head |> fst

    [ ((code "x").Value, initialRadius |> removeUnit)
      ((code "N").Value, initialNitrogen)
      ((code "bs").Value, initialMass) ]
    |> Map.ofList

Next, we set up some configuration settings, then wrap up our hypotheses and shrubs as work packages for the orchestrator, where a work package is a Async<ModelSystem.EstimationResult>.

The function to make the work packages first sets up the common time period and custom start values for each shrub, then creates per-hypothesis work packages for that shrub.

module Config =

    let numberOfReplicates = 3
    let resultsDirectory = "results/test/"
    let thinTrace = Some 100
    let endWhen = Optimisation.EndConditions.afterIteration 100000


// Function to scaffold work packages
let workPackages shrubs (hypotheses: Hypotheses.Hypothesis list) engine saveDirectory =
    seq {
        for s in shrubs do

            // 1. Arrange the subject and settings
            let shrub = s |> PlantIndividual.toCumulativeGrowth
            let common = shrub |> PlantIndividual.keepCommonYears
            let startDate = (common.Environment.[(code "N").Value]).StartDate |> snd
            let startConditions = startValues startDate shrub
            let e = engine |> Bristlecone.withConditioning (Conditioning.Custom startConditions)

            // 2. Setup batches of dependent analyses
            for h in hypotheses do
                for _ in [ 1 .. Config.numberOfReplicates ] do
                    yield
                        async {
                            // A. Compute result
                            let result =
                                Bristlecone.tryFit e Config.endWhen (Bristlecone.fromDendro common) h.Model
                            // B. Save to file
                            match result with
                            | Ok r ->
                                Bristlecone.Data.EstimationResult.saveAll
                                    saveDirectory
                                    s.Identifier.Value
                                    h.ReferenceCode
                                    Config.thinTrace
                                    r

                                return r
                            | Error e -> return failwithf "Error in package: %s" e
                        }
    }

let work = workPackages dataset hypotheses engine Config.resultsDirectory

// Calling this function will run all of the analyses, but for this
// example script we won't run them here.
let run () =
    work
    |> Seq.iter (Orchestration.OrchestrationMessage.StartWorkPackage >> orchestrator.Post)

Model selection

After calling run (), we should have a folder containing csv files, three for each EstimationResult. We can load all of these in at once to calculate model comparison statistics.

Functions for loading data are in the Bristlecone.Data namespace. You may notice that in the work package definition above we used functions from this namespace to save the results.

let saveDiagnostics () =

    // 1. Get all results sliced by plant and hypothesis
    let results =
        let get (subject: PlantIndividual.PlantIndividual) (hypothesis: Hypotheses.Hypothesis) =
            Bristlecone.Data.EstimationResult.loadAll
                Config.resultsDirectory
                subject.Identifier.Value
                hypothesis.Model
                hypothesis.ReferenceCode

        Bristlecone.ModelSelection.ResultSet.arrangeResultSets dataset hypotheses get

    // 2. Save convergence statistics to file
    results
    |> Diagnostics.Convergence.gelmanRubinAll
        10000
        (fun (s: PlantIndividual.PlantIndividual) -> s.Identifier.Value)
        (fun (h: Hypotheses.Hypothesis) -> h.ReferenceCode)
    |> Data.Convergence.save Config.resultsDirectory

    // 3. Save Akaike weights to file
    results
    |> ModelSelection.Akaike.akaikeWeightsForSet (fun (h: Hypotheses.Hypothesis) -> h.ReferenceCode)
    |> Seq.map (fun (x, a, b, c) -> x.Identifier.Value, a, b, c)
    |> Data.ModelSelection.save Config.resultsDirectory


    // // 4. Save out logged components
    // results
    // |> Seq.map(fun r ->
    //    Diagnostics.ModelComponents.calculateComponents fit engine r)

    // 5. One-step ahead predictions

    let bestFits =
        Seq.allPairs dataset hypotheses
        |> Seq.map (fun (s, h) ->
            s, h, Bristlecone.Data.MLE.loadBest Config.resultsDirectory s.Identifier.Value h.Model h.ReferenceCode)

    let oneStepPredictions =
        bestFits
        |> Seq.map (fun (s, h, mle) ->

            // 0. Convert x into biomass
            let preTransform (data: CodedMap<TimeSeries<float>>) =
                data
                |> Map.toList
                |> List.collect (fun (k, v) ->
                    if k.Value = "x" then
                        [ (k, v)
                          ((code "bs").Value,
                           v |> TimeSeries.map (fun (x, _) -> x * 1.<mm> |> Allometric.Proxies.toBiomassMM)) ]
                    else
                        [ (k, v) ])
                |> Map.ofList

            // 1. Arrange the subject and settings (same as in model-fitting)
            let shrub = s |> PlantIndividual.toCumulativeGrowth
            let common = shrub |> PlantIndividual.keepCommonYears
            let startDate = (common.Environment.[(code "N").Value]).StartDate |> snd
            let startConditions = startValues startDate shrub

            let e =
                engine
                |> Bristlecone.withConditioning (Bristlecone.Conditioning.Custom startConditions)

            let result =
                Bristlecone.oneStepAhead e h.Model preTransform (Bristlecone.fromDendro common) (mle |> snd |> snd)

            // Save each n-step ahead result to a csv file
            Bristlecone.Data.NStepAhead.save
                Config.resultsDirectory
                s.Identifier.Value
                h.ReferenceCode
                (mle |> fst)
                1
                result

            s.Identifier.Value, h.ReferenceCode, result)

    Bristlecone.Data.NStepAhead.saveAllRMSE Config.resultsDirectory oneStepPredictions

    ()
module Allometric from Shrub-resource
Multiple items
module Bristlecone from Bristlecone

--------------------
namespace Bristlecone
val pi: float
namespace System
type Math = static member Abs: value: decimal -> decimal + 7 overloads static member Acos: d: float -> float static member Acosh: d: float -> float static member Asin: d: float -> float static member Asinh: d: float -> float static member Atan: d: float -> float static member Atan2: y: float * x: float -> float static member Atanh: d: float -> float static member BigMul: a: int * b: int -> int64 + 2 overloads static member BitDecrement: x: float -> float ...
<summary>Provides constants and static methods for trigonometric, logarithmic, and other common mathematical functions.</summary>
field System.Math.PI: float = 3.14159265359
<summary>Represents the ratio of the circumference of a circle to its diameter, specified by the constant, π.</summary>
val k5: float
val k6: float
val a: float
val p: float
val lmin: float
val rtip: float
val b: float
val salixWoodDensity: float
val numberOfStems: float
val nthroot: n: float -> A: float -> float
val n: float
val A: float
val f: x: float -> float
val x: float
val m: float
val x': float
val abs: value: 'T -> 'T (requires member Abs)
val t: float
Multiple items
val double: value: 'T -> double (requires member op_Explicit)

--------------------
type double = System.Double

--------------------
type double<'Measure> = float<'Measure>
val basalRadius: k5: float -> k6: float -> stemLength: float -> float
 Gives the basal radius in centimetres of a stem/branch given its length in centimetres. Function from Niklas and Spatz (2004).
 The basal radius is always positive.
val stemLength: float
val max: e1: 'T -> e2: 'T -> 'T (requires comparison)
val stemLength: k5: float -> k6: float -> radius: float -> float
 Inverse equation of basalRadius.
val radius: float
val shrubVolume: b: float -> a: float -> rtip: float -> p: float -> lmin: float -> k5: float -> k6: float -> n: float -> h: float -> float * float
 Total shrub volume given height and number of stems
val h: float
val radius: (float -> float)
module NiklasAndSpatz_Allometry from Shrub-resource.Allometric
val mainStemVolume: float
val r: float
val mutable volume: float
val mutable k: float
val volToAdd: float
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = System.Double

--------------------
type float<'Measure> = float
module Götmark2016_ShrubModel from Shrub-resource.Allometric
namespace Bristlecone.Dendro
val private removeUnit: x: float<'u> -> float
val x: float<'u>
val mass: woodDensity: float -> volume: float -> float
val woodDensity: float
val volume: float
val massToVolume: woodDensity: float -> mass: float -> float
val mass: float
val shrubBiomass: b: float -> a: float -> rtip: float -> p: float -> lmin: float -> k5: float -> k6: float -> n: float -> woodDensity: float -> radius: float<mm> -> float
val radius: float<mm>
[<Measure>] type mm
val snd: tuple: ('T1 * 'T2) -> 'T2
val shrubRadius: b: float -> a: float -> rtip: float -> p: float -> lmin: float -> k5: float -> k6: float -> n: float -> woodDensity: float -> mass: float -> float
val findRadius: volume: float -> float
val v: x: float -> float
namespace Bristlecone.Statistics
module RootFinding from Bristlecone.Statistics
<summary> Statistics for determining the root of non-linear equations. </summary>
val bisect: n: int -> N: int -> f: (float -> float) -> a: float -> b: float -> t: float -> float
<summary> Bisect method for finding root of non-linear equations. </summary>
val shrubHeight: k5: float -> k6: float -> radius: float -> float
val toBiomassMM: radius: float<mm> -> float
 Radius in millimetres
module Allometrics from Shrub-resource.Allometric
module Constants from Shrub-resource.Allometric
val toRadiusMM: biomassGrams: float -> float
 Biomass in grams.
val biomassGrams: float
[<Struct>] type Double = member CompareTo: value: float -> int + 1 overload member Equals: obj: float -> bool + 1 overload member GetHashCode: unit -> int member GetTypeCode: unit -> TypeCode member ToString: unit -> string + 3 overloads member TryFormat: destination: Span<char> * charsWritten: byref<int> * ?format: ReadOnlySpan<char> * ?provider: IFormatProvider -> bool static member (<) : left: float * right: float -> bool static member (<=) : left: float * right: float -> bool static member (<>) : left: float * right: float -> bool static member (=) : left: float * right: float -> bool ...
<summary>Represents a double-precision floating-point number.</summary>
System.Double.IsNaN(d: float) : bool
System.Double.IsInfinity(d: float) : bool
System.Double.IsNegativeInfinity(d: float) : bool
val nan: float
val radiusCm: float
namespace Bristlecone
module Language from Bristlecone
<summary> An F# Domain Specific Language (DSL) for scripting with Bristlecone. </summary>
module Time from Bristlecone
union case ModelExpression.Constant: float -> ModelExpression
union case ModelExpression.Environment: string -> ModelExpression
val geom: (ModelExpression -> ModelExpression)
val nLimitation: (ModelExpression -> ModelExpression)
Multiple items
union case ModelExpression.Parameter: string -> ModelExpression

--------------------
module Parameter from Bristlecone
union case ModelExpression.This: ModelExpression
val feedback: (ModelExpression -> ModelExpression)
val limitationName: string
val stemRadius: lastRadius: float -> lastEnv: CodedMap<float> -> env: CodedMap<float> -> float
val lastRadius: float
val lastEnv: CodedMap<float>
val env: CodedMap<float>
val oldCumulativeMass: float
val lookup: name: string -> map: CodedMap<float> -> float
val newCumulativeMass: float
module Proxies from Shrub-resource.Allometric
val geometricConstraint: (ModelExpression -> ModelExpression)
val plantSoilFeedback: (ModelExpression -> ModelExpression)
val nLimitMode: string
module Model from Bristlecone.Language
<summary> Terms for scaffolding a model system for use with Bristlecone. </summary>
val empty: ModelBuilder.ModelBuilder
val addEquation: name: string -> eq: ModelExpression -> builder: ModelBuilder.ModelBuilder -> ModelBuilder.ModelBuilder
val includeMeasure: name: string -> measure: ModelSystem.MeasureEquation -> builder: ModelBuilder.ModelBuilder -> ModelBuilder.ModelBuilder
val estimateParameter: name: string -> constraintMode: Parameter.Constraint -> lower: float -> upper: float -> builder: ModelBuilder.ModelBuilder -> ModelBuilder.ModelBuilder
val notNegative: Parameter.Constraint
val useLikelihoodFunction: likelihoodFn: ModelSystem.LikelihoodFn -> builder: ModelBuilder.ModelBuilder -> ModelBuilder.ModelBuilder
namespace Bristlecone.ModelLibrary
module Likelihood from Bristlecone.ModelLibrary
<summary>Likelihood functions to represent a variety of distributions and data types.</summary>
<namespacedoc><summary>Pre-built model parts for use in Bristlecone</summary></namespacedoc>
val bivariateGaussian: key1: string -> key2: string -> paramAccessor: ModelSystem.ParameterValueAccessor -> data: CodedMap<ModelSystem.PredictedSeries> -> float
<summary> Log likelihood function for dual simultaneous system, assuming Gaussian error for both x and y. Requires parameters 'σ[x]', 'σ[y]' and 'ρ' to be included in any `ModelSystem` that uses it. </summary>
val noConstraints: Parameter.Constraint
val modelComponent: name: 'a -> list: 'b -> 'a * 'b
val subComponent: name: 'a -> expression: ('b -> ModelExpression) -> 'a * PluggableComponent<'b>
<summary> Creates a nested component that can be inserted into a base model. </summary>
val mass: ModelExpression
val estimateParameter: name: string -> constraintMode: Parameter.Constraint -> lower: float -> upper: float -> 'a * PluggableComponent<'b> -> 'a * PluggableComponent<'b>
val biomassLoss: biomass: ModelExpression -> ModelExpression
val biomass: ModelExpression
val saturating: minimumNutrient: ModelExpression -> nutrient: ModelExpression -> ModelExpression
val minimumNutrient: ModelExpression
val nutrient: ModelExpression
val hollingModel: n: ModelExpression -> ModelExpression
val n: ModelExpression
union case ModelExpression.Conditional: ((ModelExpression -> float) -> ModelExpression) -> ModelExpression
val compute: (ModelExpression -> float)
union case ModelExpression.Invalid: ModelExpression
val linear: min: ModelExpression -> resource: ModelExpression -> ModelExpression
val min: ModelExpression
val resource: ModelExpression
val hypotheses: Hypotheses.Hypothesis list
module Hypotheses from Bristlecone.Language
<summary>Types to represent a hypothesis, given that a hypothesis is a model system that contains some alternate formulations of certain components.</summary>
val createFromComponent: string * (string * PluggableComponent<'a>) list -> builder: (('a -> ModelExpression) -> 'b) -> Writer.Writer<'b,(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list
<summary>Implement a component where a model system requires one. A component is a part of a model that may be varied, for example between competing hypotheses.</summary>
<param name="comp">A tuple representing a component scaffolded with the `modelComponent` and `subComponent` functions.</param>
<param name="builder">A builder started with `createFromComponent`</param>
<typeparam name="'a"></typeparam>
<typeparam name="'b"></typeparam>
<returns>A builder to add further components or compile with `Hypothesis.compile`</returns>
val useAnother: string * (string * PluggableComponent<'a>) list -> builder: Writer.Writer<(('a -> ModelExpression) -> 'b),(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list -> Writer.Writer<'b,(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list
<summary>Add a second or further component to a model system where one is still required.</summary>
<param name="comp">A tuple representing a component scaffolded with the `modelComponent` and `subComponent` functions.</param>
<param name="builder">A builder started with `createFromComponent`</param>
<typeparam name="'a"></typeparam>
<typeparam name="'b"></typeparam>
<returns>A builder to add further components or compile with `Hypothesis.compile`</returns>
val useAnotherWithName: string * (string * PluggableComponent<'a>) list -> builder: Writer.Writer<(string * ('a -> ModelExpression) -> 'b),(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list -> Writer.Writer<'b,(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list
<summary>Add a second or further component to a model system where one is still required.</summary>
<param name="comp">A tuple representing a component scaffolded with the `modelComponent` and `subComponent` functions.</param>
<param name="builder">A builder started with `createFromComponent`</param>
<typeparam name="'a"></typeparam>
<typeparam name="'b"></typeparam>
<returns>A builder to add further components or compile with `Hypothesis.compile`</returns>
val compile: builder: Writer.Writer<ModelBuilder.ModelBuilder,(Hypotheses.ComponentName * CodedMap<Parameter.Parameter>)> list -> Hypotheses.Hypothesis list
<summary>Compiles a suite of competing model hypotheses based on the given components. The compilation includes only the required parameters in each model hypothesis, and combines all labels into a single model identifier.</summary>
<param name="builder">A builder started with `createFromComponent`</param>
<returns>A list of compiled hypotheses for this model system and specified components.</returns>
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
property List.Length: int with get
Multiple items
module Seq from Bristlecone

--------------------
module Seq from Microsoft.FSharp.Collections
val iteri: action: (int -> 'T -> unit) -> source: 'T seq -> unit
val i: int
val h: Hypotheses.Hypothesis
property Hypotheses.Hypothesis.ReferenceCode: string with get
<summary>Compiles a reference code that may be used to identify (although not necessarily uniquely) this hypothesis</summary>
<returns>A string in the format XX_XXX_YY_YYY... where XX_XXX is a singe component with XX the component and XXX the implementation.</returns>
property Hypotheses.Hypothesis.Components: Hypotheses.ComponentName list with get
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val x: Hypotheses.ComponentName
Hypotheses.ComponentName.Implementation: string
module String from Microsoft.FSharp.Core
val concat: sep: string -> strings: string seq -> string
val output: (Logging.LogEvent -> unit)
namespace Bristlecone.Logging
module Console from Bristlecone.Logging
<summary> Simple logger to console that prints line-by-line progress and events. </summary>
val logger: nIteration: int -> (Logging.LogEvent -> unit)
<summary> A simple console logger. `nIteration` specifies the number of iterations after which to log the current likelihood and parameter values. </summary>
val engine: EstimationEngine.EstimationEngine<float,float>
val mkContinuous: EstimationEngine.EstimationEngine<float,float>
<summary>A basic estimation engine for ordinary differential equations, using a Nelder-Mead optimiser.</summary>
val withContinuousTime: t: EstimationEngine.Integrate<'a,'b> -> engine: EstimationEngine.EstimationEngine<'a,'b> -> EstimationEngine.EstimationEngine<'a,'b>
<summary> Use a custom integration method </summary>
namespace Bristlecone.Integration
module MathNet from Bristlecone.Integration
val integrate: log: (Logging.LogEvent -> unit) -> tInitial: float -> tEnd: float -> tStep: float -> initialConditions: Map<'a,float> -> externalEnvironment: Map<'a,TimeIndex.TimeIndex<float>> -> modelMap: Map<'a,(float -> float -> Map<'a,float> -> float)> -> Map<'a,float array> (requires comparison)
val withOutput: out: EstimationEngine.WriteOut -> engine: EstimationEngine.EstimationEngine<'a,'b> -> EstimationEngine.EstimationEngine<'a,'b>
<summary>Substitute a specific logger into</summary>
<param name="out"></param>
<param name="engine"></param>
<typeparam name="'a"></typeparam>
<typeparam name="'b"></typeparam>
<returns></returns>
val withConditioning: c: Conditioning.Conditioning<'a> -> engine: EstimationEngine.EstimationEngine<'a,'b> -> EstimationEngine.EstimationEngine<'a,'b>
<summary> Choose how the start point is chosen when solving the model system </summary>
module Conditioning from Bristlecone
union case Conditioning.Conditioning.RepeatFirstDataPoint: Conditioning.Conditioning<'a>
val withCustomOptimisation: optim: EstimationEngine.Optimiser<'a> -> engine: EstimationEngine.EstimationEngine<'a,'b> -> EstimationEngine.EstimationEngine<'a,'b>
namespace Bristlecone.Optimisation
module MonteCarlo from Bristlecone.Optimisation
<summary> A module containing Monte Carlo Markov Chain (MCMC) methods for optimisation. An introduction to MCMC approaches is provided by [Reali, Priami, and Marchetti (2017)](https://doi.org/10.3389/fams.2017.00006) </summary>
module SimulatedAnnealing from Bristlecone.Optimisation.MonteCarlo
<summary> A meta-heuristic that approximates a global optimium by simulating slow cooling as a slow decrease in the probability of temporarily accepting worse solutions. </summary>
val fastSimulatedAnnealing: scale: float -> tDependentProposal: bool -> settings: Optimisation.MonteCarlo.SimulatedAnnealing.AnnealSettings<float> -> EstimationEngine.Optimiser<float>
<summary> Candidate distribution: Cauchy univariate [] Probability: Bottzmann Machine </summary>
type AnnealSettings<'a> = { HeatStepLength: EndCondition<'a> HeatRamp: (float -> float) TemperatureCeiling: float option BoilingAcceptanceRate: float InitialTemperature: float PreTuneLength: int TuneLength: int TuneN: int AnnealStepLength: EndCondition<'a> } static member Default: AnnealSettings<float>
<summary> Represents configurable settings of an annealing procedure that supports (a) heating, followed by (b) annealing. </summary>
union case Option.Some: Value: 'T -> Option<'T>
module EndConditions from Bristlecone.Optimisation
val afterIteration: iteration: int -> EstimationEngine.Solution<float> list -> currentIteration: int -> bool
<summary> End the optimisation procedure when a minimum number of iterations is exceeded. </summary>
val x: EstimationEngine.Solution<float> list
val n: int
module EndConditions from Bristlecone.Optimisation.MonteCarlo.SimulatedAnnealing
val improvementCount: count: int -> interval: int -> results: EstimationEngine.Solution<float> list -> iteration: int -> bool
val testSettings: Test.TestSettings<float>
Multiple items
module Test from Bristlecone.Language
<summary> Terms for designing tests for model systems. </summary>

--------------------
module Test from Bristlecone
val create: Test.TestSettings<float>
val addNoise: noiseFn: (System.Random -> Parameter.Pool -> CodedMap<TimeSeries<'a>> -> Result<CodedMap<TimeSeries<'a>>,string>) -> settings: Test.TestSettings<'a> -> Test.TestSettings<'a>
<summary> Add noise to a particular time-series when generating fake time-series. Built-in noise functions are in the `Noise` module. </summary>
module Noise from Bristlecone.Test
<summary> Functions for adding background variability into test problems. </summary>
val tryAddNormal: sdParamCode: string -> seriesName: string -> rnd: System.Random -> pool: Parameter.Pool.ParameterPool -> data: CodedMap<TimeSeries.TimeSeries<float>> -> Result<Map<ShortCode.ShortCode,TimeSeries.TimeSeries<float>>,string>
<summary> Adds normally-distributed noise around each data point in the selected time-series. Returns `None` if the series or parameter does not exist. </summary>
val addGenerationRules: rules: Test.GenerationRule list -> settings: Test.TestSettings<'a> -> Test.TestSettings<'a>
module GenerationRules from Bristlecone.Test
val alwaysMoreThan: i: float -> variable: string -> Test.GenerationRule
<summary> Ensures that all generated values are greater than i </summary>
val alwaysLessThan: i: float -> variable: string -> Test.GenerationRule
<summary> Ensures that all generated values are less than i </summary>
val monotonicallyIncreasing: variable: string -> Test.GenerationRule
<summary> Ensures that there is always a positive change in values of a variable </summary>
val addStartValues: values: (string * 'a) seq -> settings: Test.TestSettings<'a> -> Test.TestSettings<'a>
<summary> Adds start values to the test settings. Overwrites any existing start values that may already exist. </summary>
val toBiomassMM: radius: float<Dendro.mm> -> float
 Radius in millimetres
val withTimeSeriesLength: n: int -> settings: Test.TestSettings<'a> -> Test.TestSettings<'a>
val endWhen: goal: EstimationEngine.EndCondition<'a> -> settings: Test.TestSettings<'a> -> Test.TestSettings<'a>
val testResult: Test.TestResult list
Multiple items
module List from Bristlecone

--------------------
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
property Hypotheses.Hypothesis.Model: ModelSystem.ModelSystem with get
val testModel: engine: EstimationEngine.EstimationEngine<float,float> -> settings: Test.TestSettings<float> -> model: ModelSystem.ModelSystem -> Test.TestResult
<summary>Test that the specified estimation engine can correctly estimate known parameters. Random parameter sets are generated from the given model system.</summary>
<param name="engine">An estimation engine containing the method used for model-fitting.</param>
<param name="settings">Test settings that define how the test will be conducted.</param>
<param name="model">The model system to test against the estimation engine.</param>
<returns>A test result that indicates differences between the expected and actual fit.</returns>
val dataset: PlantIndividual.PlantIndividual seq
val plants: PlantIndividual.PlantIndividual list
Multiple items
namespace Bristlecone.Data

--------------------
namespace Microsoft.FSharp.Data
module PlantIndividual from Bristlecone.Data
val loadRingWidths: fileName: string -> PlantIndividual.PlantIndividual list
val isotopeData: (string * TimeSeries.TimeSeries<float>) list
val loadLocalEnvironmentVariable: fileName: string -> (string * TimeSeries.TimeSeries<float>) list
module PlantIndividual from Bristlecone.Dendro
val zipEnvMany: envName: string -> env: (string * TimeSeries.TimeSeries<float>) seq -> plants: PlantIndividual.PlantIndividual seq -> PlantIndividual.PlantIndividual seq
<summary> Assigns local environmental conditions to each plant in a sequence, given a sequence of environmental time-series where each time-series has the code of the plant associated with it. </summary>
namespace Bristlecone.Workflow
val orchestrator: Orchestration.OrchestrationAgent
module Orchestration from Bristlecone.Workflow
<summary> Queue functions to manage many work packages in parallel. [ Inspired by Tom Petricek: http://fssnip.net/nX ] </summary>
Multiple items
type OrchestrationAgent = new: writeOut: (LogEvent -> unit) * maxSimultaneous: int * retainResults: bool -> OrchestrationAgent member Post: msg: OrchestrationMessage -> unit member TryGetResult: unit -> EstimationResult option
<summary> The `OrchestrationAgent` queues work items of the type `Async&lt;EstimationResult&gt;`, which are run in parallel up to a total of `maxSimultaneous` at one time. </summary>

--------------------
new: writeOut: (Logging.LogEvent -> unit) * maxSimultaneous: int * retainResults: bool -> Orchestration.OrchestrationAgent
type Environment = static member Exit: exitCode: int -> unit static member ExpandEnvironmentVariables: name: string -> string static member FailFast: message: string -> unit + 1 overload static member GetCommandLineArgs: unit -> string array static member GetEnvironmentVariable: variable: string -> string + 1 overload static member GetEnvironmentVariables: unit -> IDictionary + 1 overload static member GetFolderPath: folder: SpecialFolder -> string + 1 overload static member GetLogicalDrives: unit -> string array static member SetEnvironmentVariable: variable: string * value: string -> unit + 1 overload static member CommandLine: string ...
<summary>Provides information about, and means to manipulate, the current environment and platform. This class cannot be inherited.</summary>
property System.Environment.ProcessorCount: int with get
<summary>Gets the number of processors available to the current process.</summary>
<returns>The 32-bit signed integer that specifies the number of processors that are available.</returns>
val startValues: startDate: System.DateTime -> plant: PlantIndividual.PlantIndividual -> Map<ShortCode.ShortCode,float>
val startDate: System.DateTime
Multiple items
[<Struct>] type DateTime = new: year: int * month: int * day: int -> unit + 10 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime member AddYears: value: int -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>

--------------------
System.DateTime ()
   (+0 other overloads)
System.DateTime(ticks: int64) : System.DateTime
   (+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : System.DateTime
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : System.DateTime
   (+0 other overloads)
val plant: PlantIndividual.PlantIndividual
type PlantIndividual = { Identifier: ShortCode Growth: PlantGrowth InternalControls: Map<ShortCode,Trait> Environment: LocalEnvironment }
val removeUnit: x: float<'u> -> float
val initialRadius: float<mm>
PlantIndividual.PlantIndividual.Growth: PlantIndividual.PlantGrowth
type PlantGrowth = | RingWidth of GrowthSeries<mm> | BasalArea of GrowthSeries<mm ^ 2> | StemVolume of GrowthSeries<mm ^ 3>
union case PlantIndividual.PlantGrowth.RingWidth: GrowthSeries.GrowthSeries<mm> -> PlantIndividual.PlantGrowth
val s: GrowthSeries.GrowthSeries<mm>
module GrowthSeries from Bristlecone.Time
union case GrowthSeries.GrowthSeries.Cumulative: TimeSeries<float<'u>> -> GrowthSeries.GrowthSeries<'u>
val c: TimeSeries<float<mm>>
val trimmed: TimeSeries.TimeSeries<float<mm>> option
Multiple items
module TimeSeries from Bristlecone.Time

--------------------
type TimeSeries<'T> = TimeSeries.TimeSeries<'T>
val trimStart: startDate: System.DateTime -> series: TimeSeries.TimeSeries<'a> -> TimeSeries.TimeSeries<'a> option
<summary> Remove all time points that occur before the desired start date. </summary>
Multiple items
[<Struct>] type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 3 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>

--------------------
System.TimeSpan ()
System.TimeSpan(ticks: int64) : System.TimeSpan
System.TimeSpan(hours: int, minutes: int, seconds: int) : System.TimeSpan
System.TimeSpan(days: int, hours: int, minutes: int, seconds: int) : System.TimeSpan
System.TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : System.TimeSpan
System.TimeSpan.FromDays(value: float) : System.TimeSpan
val t: TimeSeries.TimeSeries<float<mm>>
property TimeSeries.TimeSeries.Values: float<mm> seq with get
val head: source: 'T seq -> 'T
union case Option.None: Option<'T>
val failwith: message: string -> 'T
val invalidOp: message: string -> 'T
val initialMass: float
val initialNitrogen: float
PlantIndividual.PlantIndividual.Environment: EnvironmentalVariables.LocalEnvironment
val code: (string -> ShortCode.ShortCode option)
<summary> A short code representation of an identifier for a parameter, model equation, or other model component. </summary>
val fst: tuple: ('T1 * 'T2) -> 'T1
Multiple items
module Map from Bristlecone

--------------------
module Map from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...

--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
val ofList: elements: ('Key * 'T) list -> Map<'Key,'T> (requires comparison)
val numberOfReplicates: int
val resultsDirectory: string
val thinTrace: int option
val endWhen: EstimationEngine.EndCondition<float>
val workPackages: shrubs: PlantIndividual.PlantIndividual seq -> hypotheses: Hypotheses.Hypothesis list -> engine: EstimationEngine.EstimationEngine<float,float> -> saveDirectory: string -> Async<ModelSystem.EstimationResult> seq
val shrubs: PlantIndividual.PlantIndividual seq
Multiple items
union case Hypotheses.Hypothesis.Hypothesis: ModelSystem.ModelSystem * Hypotheses.ComponentName list -> Hypotheses.Hypothesis

--------------------
type Hypothesis = | Hypothesis of ModelSystem * ComponentName list member Components: ComponentName list member Model: ModelSystem member ReferenceCode: string
<summary>A hypothesis consists of a model system and the names of the swappable components within it, alongside the name of their current implementation.</summary>
type 'T list = List<'T>
val saveDirectory: string
Multiple items
val seq: sequence: 'T seq -> 'T seq

--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val s: PlantIndividual.PlantIndividual
val shrub: PlantIndividual.PlantIndividual
val toCumulativeGrowth: plant: PlantIndividual.PlantIndividual -> PlantIndividual.PlantIndividual
val common: PlantIndividual.PlantIndividual
val keepCommonYears: plant: PlantIndividual.PlantIndividual -> PlantIndividual.PlantIndividual
<summary> Where a plant has associated environmental data, discard the beginning or end of the growth and environment time-series where not all data are present. </summary>
val startConditions: Map<ShortCode.ShortCode,float>
val e: EstimationEngine.EstimationEngine<float,float>
union case Conditioning.Conditioning.Custom: CodedMap<'a> -> Conditioning.Conditioning<'a>
module Config from Shrub-resource
val async: AsyncBuilder
val result: Result<ModelSystem.EstimationResult,string>
val tryFit: engine: EstimationEngine.EstimationEngine<float,float> -> endCondition: EstimationEngine.EndCondition<float> -> timeSeriesData: CodedMap<TimeSeries<float>> -> model: ModelSystem.ModelSystem -> Result<ModelSystem.EstimationResult,string>
<summary> Fit a time-series model to data. Please note: it is strongly recommended that you test that the given `EstimationEngine` can correctly identify known parameters for your model. Refer to the `Bristlecone.testModel` function, which can be used to generate known data and complete this process. </summary>
<param name="engine">The engine encapsulates all settings that form part of the estimation method. Importantly, this includes the random number generator used for all stages of the analysis; if this is set using a fixed seed, the result will be reproducable.</param>
<param name="endCondition">You must specify a stopping condition, after which the optimisation process will cease. Bristlecone includes built-in end conditions in the `Bristlecone.Optimisation.EndConditions` module.</param>
<param name="timeSeriesData"></param>
<param name="model"></param>
<returns></returns>
val fromDendro: plant: PlantIndividual.PlantIndividual -> Map<ShortCode.ShortCode,TimeSeries<float>>
<summary>Lower time-series data from a plant individual type into basic time-series that may be used for model-fitting</summary>
<param name="plant">A plant individual</param>
<returns>A coded map of time-series data for model-fitting</returns>
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
val r: ModelSystem.EstimationResult
namespace Bristlecone.Data
module EstimationResult from Bristlecone.Data
val saveAll: directory: string -> subject: string -> modelId: string -> thinTraceBy: int option -> result: ModelSystem.EstimationResult -> unit
<summary>Save the Maximum Likelihood Estimate, trace of the optimisation procedure, and time-series.</summary>
<param name="directory">Relative or absolute directory to save files to</param>
<param name="subject">An identifier for the subject of the test</param>
<param name="modelId">An identifier for the model used</param>
<param name="thinTraceBy">If Some, an integer representing the nth traces to keep from the optimisation trace. If None, does not thin the trace.</param>
<param name="result">The estimation result to save</param>
PlantIndividual.PlantIndividual.Identifier: ShortCode.ShortCode
property ShortCode.ShortCode.Value: string with get
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val e: string
val failwithf: format: Printf.StringFormat<'T,'Result> -> 'T
val work: Async<ModelSystem.EstimationResult> seq
val run: unit -> unit
val iter: action: ('T -> unit) -> source: 'T seq -> unit
type OrchestrationMessage = | StartWorkPackage of Async<EstimationResult> | StartDependentWorkPackages of Async<EstimationResult> | Finished of EstimationResult | WorkFailed of exn | WorkCancelled
union case Orchestration.OrchestrationMessage.StartWorkPackage: Async<ModelSystem.EstimationResult> -> Orchestration.OrchestrationMessage
member Orchestration.OrchestrationAgent.Post: msg: Orchestration.OrchestrationMessage -> unit
val saveDiagnostics: unit -> unit
val results: ModelSelection.ResultSet.ResultSet<PlantIndividual.PlantIndividual,Hypotheses.Hypothesis> seq
val get: subject: PlantIndividual.PlantIndividual -> hypothesis: Hypotheses.Hypothesis -> ModelSystem.EstimationResult seq
val subject: PlantIndividual.PlantIndividual
val hypothesis: Hypotheses.Hypothesis
val loadAll: directory: string -> subject: string -> modelSystem: ModelSystem.ModelSystem -> modelId: string -> ModelSystem.EstimationResult seq
<summary> Load an `EstimationResult` that has previously been saved as three seperate dataframes. Results will only be reconstructed when file names and formats are in original Bristlecone format. </summary>
module ModelSelection from Bristlecone
<summary>Contains tools for conducting Model Selection across individual subjects and hypotheses.</summary>
module ResultSet from Bristlecone.ModelSelection
<summary>Organises multiple hypotheses and multiple subjects into distinct analysis groups.</summary>
val arrangeResultSets: subjects: 'subject seq -> hypotheses: 'hypothesis seq -> getResults: ('subject -> 'hypothesis -> ModelSystem.EstimationResult seq) -> ModelSelection.ResultSet.ResultSet<'subject,'hypothesis> seq
<summary>Arrange estimation results into subject and hypothesis groups.</summary>
<param name="subjects"></param>
<param name="hypotheses"></param>
<param name="getResults"></param>
<typeparam name="'subject"></typeparam>
<typeparam name="'hypothesis"></typeparam>
<typeparam name="'a">An estimation result</typeparam>
<returns></returns>
module Diagnostics from Bristlecone
<summary>Diagnostic techniques for determining the suitability of results obtained with Bristlecone.</summary>
module Convergence from Bristlecone.Diagnostics
<summary>Convergence diagnostics for monte-carlo markov chain (MCMC) analyses.</summary>
val gelmanRubinAll: nMostRecent: int -> subject: ('subject -> string) -> hypothesis: ('hypothesis -> string) -> results: ModelSelection.ResultSet.ResultSet<'subject,'hypothesis> seq -> Diagnostics.Convergence.ConvergenceStatistic seq
<summary>Calculate the Gelman-Rubin statistic for each parameter in all of the given `ResultSet`. The statistic tends downwards to one, with one indicating perfect convergence between all chains.</summary>
<param name="nMostRecent">How many recent iterations to use from the trace.</param>
<param name="subject">A function to retrieve a subject ID from a subject</param>
<param name="hypothesis">A function to retrieve a hypothesis ID from a hypothesis</param>
<param name="result">A result set (of 1 .. many results) for a particular subject and hypothesis</param>
<returns>If more than one replicate, the R-hat convergence statistic across replicates</returns>
module Convergence from Bristlecone.Data
val save: directory: string -> result: Diagnostics.Convergence.ConvergenceStatistic seq -> unit
module Akaike from Bristlecone.ModelSelection
<summary>Functions for conducting Akaike Information Criterion (AIC).</summary>
val akaikeWeightsForSet: getRefCode: ('a -> 'b) -> set: ModelSelection.ResultSet.ResultSet<'c,'a> seq -> ('c * 'b * ModelSystem.EstimationResult * ModelSelection.Akaike.AkaikeWeight) list (requires equality)
<summary>Akaike weights for a result set.</summary>
<param name="getRefCode">A function that gets a short reference code from a hypothesis.</param>
<param name="set">A sequence of `ResultSet`s, within each the 1 .. many results of a particular subject * hypothesis combination.</param>
<returns>An `(EstimationResult * float) seq` of estimation results paired to their Akaike weights.</returns>
<exception cref="ArgumentException">Occurs when there are no observations within an estimation result.</exception>
val x: PlantIndividual.PlantIndividual
val a: string
val b: ModelSystem.EstimationResult
val c: ModelSelection.Akaike.AkaikeWeight
module ModelSelection from Bristlecone.Data
val save: directory: string -> result: (string * string * ModelSystem.EstimationResult * ModelSelection.Akaike.AkaikeWeight) seq -> unit
val bestFits: (PlantIndividual.PlantIndividual * Hypotheses.Hypothesis * (System.Guid * (float * Parameter.Pool.ParameterPool))) seq
val allPairs: source1: 'T1 seq -> source2: 'T2 seq -> ('T1 * 'T2) seq
module MLE from Bristlecone.Data
<summary>Functions of loading and saving Maximum Likelihood Estimates from model fits</summary>
val loadBest: directory: string -> subject: string -> modelSystem: ModelSystem.ModelSystem -> modelId: string -> System.Guid * (float * Parameter.Pool.ParameterPool)
<summary>Load the Maximum Likelihood Estimate (with the lowest -log Liklihood) from the given directory for a given subject and model combination.</summary>
<param name="directory">The results folder where files are saved</param>
<param name="subject">An identifier for the subject of the fit</param>
<param name="modelId">An identifier for the model that was fit</param>
<returns>A tuple containing the analysis ID followed by another tuple that contains the likelihood and theta (parameter set)</returns>
val oneStepPredictions: (string * string * CodedMap<ModelSystem.FitSeries * Statistics.NStepStatistics>) seq
val mle: System.Guid * (float * Parameter.Pool.ParameterPool)
val preTransform: data: CodedMap<TimeSeries<float>> -> Map<ShortCode.ShortCode,TimeSeries<float>>
val data: CodedMap<TimeSeries<float>>
type CodedMap<'T> = Map<ShortCode.ShortCode,'T>
val toList: table: Map<'Key,'T> -> ('Key * 'T) list (requires comparison)
val collect: mapping: ('T -> 'U list) -> list: 'T list -> 'U list
val k: ShortCode.ShortCode
val v: TimeSeries<float>
val map: f: ('a * System.TimeSpan -> 'b) -> series: TimeSeries.TimeSeries<'a> -> TimeSeries.TimeSeries<'b>
<summary> Map a function to each value in the time series. </summary>
val result: CodedMap<ModelSystem.FitSeries * Statistics.NStepStatistics>
val oneStepAhead: engine: EstimationEngine.EstimationEngine<float,float> -> hypothesis: ModelSystem.ModelSystem -> preTransform: (CodedMap<TimeSeries<'a>> -> CodedMap<TimeSeries<float>>) -> timeSeries: Map<ShortCode.ShortCode,TimeSeries.TimeSeries<'a>> -> estimatedTheta: Parameter.Pool -> CodedMap<ModelSystem.FitSeries * Statistics.NStepStatistics>
<summary>Addresses the question: "How good am I at predicting the next data point?. Given fitted parameters, assesses how well the model predicts the next data point from each point in the time-series data. This approach can provide another indication of model performance.</summary>
<param name="engine">The exact estimation engine used for the existing model fit</param>
<param name="hypothesis">The exact model system / hypothesis from which parameters have been already estimated</param>
<param name="preTransform">A function that may transform each shorter time-series before prediction. This may be needed, for example, if there are custom start values that need to be configured in a complex way (e.g. for derived mesaurement variables).</param>
<param name="timeSeries">The observed data to predict against.</param>
<param name="estimatedTheta">A parameter pool containing already estimated parameters from model fitting step</param>
<returns>A time-series for each variable containing a step-ahead prediction</returns>
module NStepAhead from Bristlecone.Data
<summary>Loads and saves one-step-ahead and n-step-ahead prediction results into and out of simple CSV files</summary>
val save: directory: string -> subjectId: string -> modelId: string -> analysisId: System.Guid -> stepsAhead: int -> result: CodedMap<ModelSystem.FitSeries * Statistics.NStepStatistics> -> unit
<summary>Save an individual n-step prediction (excluding statistics) for an individual subject and hypothesis / model.</summary>
<param name="directory">The file directory to save to</param>
<param name="subjectId">Subject ID</param>
<param name="modelId">Model ID or Hypothesis ID</param>
<param name="analysisId">The unique reference of the estimation result used to generate n-step predictions</param>
<param name="stepsAhead">The number of time-steps ahead predicted</param>
<param name="result">The n-step prediction from Bristlecone.oneStepAhead or similar</param>
val saveAllRMSE: directory: string -> results: (string * string * CodedMap<ModelSystem.FitSeries * Statistics.NStepStatistics>) seq -> unit
<summary>Saves a full ensemble overview of the root mean squared error of n-step ahead predictions.</summary>
<param name="directory">The file directory to save to</param>
<param name="results">A sequence of subjectID * hypothesisID * n-step result</param>

Type something to start searching.