Defining an Estimation Engine
What is an estimation engine?
An estimation engine in Bristlecone defines how parameters of models are estimated from data. It's key elements are:
- the time mode (discrete- or continuous-time),
- the integration method (for continuous-time models),
- the optimisation or sampling method,
- the conditioning strategy,
- the random seed and reproducibility settings.
A single engine should typically be used across all model hypotheses in a study so that comparisons are fair and reproducible.
Bristlecone provides defaults for most components, but all parts are configurable.
Below, the main configurable parts are outlined. Please check the API docs for further details on each option.
Estimation engines are composed using functions within the Bristlecone module, as below.
Creating an engine
Bristlecone supports two ways of evolving a model through time:
Discrete-time engine
The model is evaluated once per time step. This is appropriate for any system where the process is naturally discrete.
Call Bristlecone.mkDiscrete () for this engine type.
Continuous-time engine
The model is defined as a system of ordinary differential equations (ODEs) and must be integrated numerically.
Call Bristlecone.mkContinuous () for this engine type.
ODE model systems are appropriate for processes that are most naturally represented as rates. Bristlecone will solve continuous-time models dependent on the time-steps of the datasets provided, whether those are on fixed time-stepping (e.g. annual ring widths) or variable time-stepping (e.g. sediment core data).
Bristlecone includes a classical Runge-Kutta integrator as the default for continuous-time models; see integration methods for more details.
Example:
let engine: EstimationEngine.EstimationEngine<System.DateTime,System.TimeSpan,Time.year,1> =
Bristlecone.mkContinuous ()
Customising an engine
All engine components can be modified using the builder functions in the Bristlecone module.
Choosing an optimisation method
Use:
Bristlecone.withCustomOptimisation
to supply any optimiser with the correct type signature.
The default optimisation method is 'Bristlecone-style annealing'; for details of this method and others see optimisation methods for more details.
Convenience functions include:
Bristlecone.withGradientDescent- Nelder–Mead simplex.Bristlecone.withTunedMCMC— homogeneous random‑walk MCMC with optional tuning steps.Bristlecone.withBristleconeOptimiser- Bristlecone’s hybrid simulated‑annealing + MCMC optimiser.
Example:
engine |> Bristlecone.withCustomOptimisation Optimisation.MonteCarlo.``Adaptive-Metropolis-within Gibbs``
Conditioning the first time-point
The data may be conditioned such that the value of the data in the first point in the time-series is not lost. There are three in-built conditioning modes:
Conditioning.NoConditioning(default). The first time point is used as the starting point (t0) in fitting.Conditioning.RepeatFirstDataPoint. The first time point is repeated so that it is used as both t0 and t1.Conditioning.Custom. Specify custom starting conditions.
Example:
engine |> Bristlecone.withConditioning Conditioning.RepeatFirstDataPoint
Connecting model time to data time
The temporal resolution of the model must be linked to the temporal system of the data. Bristlecone's models and engines are strongly typed such that conformity of time systems is enforced by the compiler.
To link your model and data time, the Bristlecone builders contains a Bristlecone.withTimeConversion function. To illustrate, a model may be defined at annual resolution, with the data defined in radiocarbon dates. The engine just requires a simple conversion function so that it understands how radiocarbon dates relate to years. In the Bristlecone.Time.DateMode.Conversion module, each built-in time system (e.g. radiocarbon, annual, .NET calendars) has conversion functions to relate these to the basic temporal resolutions of day, month, and year.
For example, the below conversion makes an estimation engine that can directly consume a model that is defined in monthly resolution, and requires data defined in standard .NET DateTime times.
let forMonthlyModel: EstimationEngine.EstimationEngine<System.DateTime,System.TimeSpan,Time.month,1> =
Bristlecone.mkDiscrete ()
|> Bristlecone.withTimeConversion Time.DateMode.Conversion.CalendarDates.toMonths
You can see that the estimation engine has typed units of Time.month as the model resolution, and System.DateTime / System.TimeSpan as the date / timespan units.
Reproducibility
You may set a seed to ensure that any particular analysis is reproducable by calling Bristlecone.withSeed. Note that if a multi-threaded orchestrator is used, this will not guarantee reproducibility, as the orchestrator may run work packages in different orders depending on race conditions.
Logging
Use:
Bristlecone.withOutput
to set a custom logger. The default is a simple console logger that outputs a trace every 1000 iterations.
More built-in loggers are defined in the Bristlecone.Logging module, such as a multi-threaded table logger.
In addition, the Bristlecone.Charts.R package defines a graphical parameter trace output to see real-time
traces during optimisation.
Example: continuous time with Nelder-Mead optimisation
let engine2: EstimationEngine.EstimationEngine<Time.DatingMethods.Annual,int<Time.year>,Time.year,1> =
Bristlecone.mkContinuous ()
|> Bristlecone.withCustomOptimisation (Optimisation.Amoeba.swarm 10 20 Optimisation.Amoeba.Solver.Default)
|> Bristlecone.withConditioning Conditioning.NoConditioning
|> Bristlecone.withSeed 1500
|> Bristlecone.withTimeConversion Time.DateMode.Conversion.Annual.toYears
Here, an estimation engine is made that uses a swarm-based Nelder-Mead (amoeba) optimisation routine to fit annual time-series data to a model defined in terms of years.
module Bristlecone from Bristlecone
<namespacedoc><summary>The core library of Bristlecone, containing model-fitting functions.</summary></namespacedoc>
Main functionality of Bristlecone, including functions to scaffold `ModelSystem`s and for model-fitting (tests and real fits).
--------------------
namespace Bristlecone
<summary> The estimation engine represents the method used to calculate equations and optimise a likelihood function. The whole estimation engine is tensor-based internally, but may take float-based equations as a legacy option. </summary>
[<Struct>] type DateTime = new: date: DateOnly * time: TimeOnly -> unit + 16 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMicroseconds: 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 ...
<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(date: System.DateOnly, time: System.TimeOnly) : System.DateTime
(+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : System.DateTime
(+0 other overloads)
System.DateTime(date: System.DateOnly, time: System.TimeOnly, 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)
[<Struct>] type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 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(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : System.TimeSpan
<summary>A basic estimation engine for ordinary differential equations, using a Nelder-Mead optimiser.</summary>
<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>
<summary> Choose how the start point is chosen when solving the model system </summary>
<summary>A basic estimation engine for discrete-time equations, using a Nelder-Mead optimiser.</summary>
<summary> Conversion functions that translate from one time unit into another. These functions are intended primarily for use in estimation engines when translating from data time to model time. </summary>
<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>
<summary> Contains types representing common dating methods in long term data analysis. </summary>
union case Time.DatingMethods.Annual.Annual: int<Time.year> -> Time.DatingMethods.Annual
--------------------
type Annual = | Annual of int<year> static member (+) : e1: Annual * e2: int<year> -> Annual static member (-) : e1: Annual * e2: Annual -> int<year> static member AddYears: date: Annual -> years: int<year> -> Annual static member FractionalDifference: isSigned: bool -> d1: Annual -> d2: Annual -> TimeDifference<int<year>> static member TotalYearsElapsed: d1: Annual -> d2: Annual -> int<year> static member Unwrap: Annual -> int<year> member Value: int<year> with get
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
<summary> Nelder Mead implementation Adapted from original at: https://github.com/mathias-brandewinder/Amoeba </summary>
<summary> Optimisation heuristic that creates a swarm of amoeba (Nelder-Mead) solvers. The swarm proceeds for `numberOfLevels` levels, constraining the starting bounds at each level to the 80th percentile of the current set of best likelihoods. </summary>
<summary> Nelder–Mead downhill simplex </summary>
<summary> Use a mersenne twister random number generator with a specific seed. </summary>
bristlecone