Example model from Chapter 10 of 'The Ecological Detective'. Models for Namibian hake.
/// The Schaefer model of fishing
module Schaefer =
let B = state<kton> "biomass"
let I = measure<1> "CPUE" // Catch per unit effort
let C = environment<kton> "catch"
let r = parameter "r" Positive 0.01</year> 1.0</year> // Intrinsic growth rate
let K = parameter "K" Positive 100.<kton> 2000.<kton> // Carrying capacity
let q = parameter "q" Positive 1e-6<1/kton> 1e-2<1/kton> // Catchability coefficient
let B0 = P K // Biomass at time zero. Assumes stock unfished at start time
let dt = Constant 1.<year>
let ``B_est[t+1]`` =
let n = State B + P r * State B * (Constant 1. - State B / P K) * dt - Environment C
Conditional (n .> Constant 0.<kton>) n (Constant 0.<kton>)
let It = P q * State B
let sigma = parameter "sigma_v" NoConstraints 0.01 1.0
let NLL = ModelLibrary.NegLogLikelihood.LogNormal (Require.measure I) sigma
let model =
Model.discrete<year>
|> Model.addDiscreteEquation B ``B_est[t+1]``
|> Model.addMeasure I It
|> Model.initialiseHiddenStateWith B B0
|> Model.estimateParameter r
|> Model.estimateParameter K
|> Model.estimateParameter q
|> Model.useLikelihoodFunction NLL
|> Model.compile
Here, we input the data from the table in the book directly into F# code.
The units are: * CPUE: tons per standardised trawler hour * Catch (thousands of tons)
let cpueData, catchData =
[
1965, 1.78, 94
1966, 1.31, 212
1967, 0.91, 195
1968, 0.96, 383
1969, 0.88, 320
1970, 0.90, 402
1971, 0.87, 366
1972, 0.72, 606
1973, 0.57, 378
1974, 0.45, 319
1975, 0.42, 309
1976, 0.42, 389
1977, 0.49, 277
1978, 0.43, 254
1979, 0.40, 170
1980, 0.45, 97
1981, 0.55, 91
1982, 0.53, 177
1983, 0.58, 216
1984, 0.64, 229
1985, 0.66, 211
1986, 0.65, 231
1987, 0.63, 223
]
|> List.map(fun (y, cpue, catch) ->
let time = DatingMethods.Annual (y * 1<year>)
(cpue, time), (float catch, time))
|> List.unzip
We then convert the raw time-series data into Bristlecone time-series
by using the fromObservations function with the annual date mode.
let ts =
[
Schaefer.I.Code, cpueData |> TimeSeries.fromObservations DateMode.annualDateMode
Schaefer.C.Code, catchData |> TimeSeries.fromObservations DateMode.annualDateMode
] |> Map.ofList
Next, we create a standard estimation engine with no data conditioning, and run a model fit.
let engine: EstimationEngine.EstimationEngine<DatingMethods.Annual,int<year>,year,1> =
Bristlecone.mkDiscrete ()
|> Bristlecone.withTimeConversion DateMode.Conversion.Annual.toYears
|> Bristlecone.withConditioning Conditioning.NoConditioning
|> Bristlecone.withOutput (Logging.Console.logger 10<iteration>)
|> Bristlecone.withCustomOptimisation (Optimisation.MonteCarlo.``Automatic (Adaptive Diagnostics)``)
let r =
Bristlecone.fit engine (Optimisation.EndConditions.atIteration 1000<iteration>) ts Schaefer.model
Assuming observation uncertainty as above, the book gives parameter estimates of r=0.39, K=2709, q=0.00045, and sigmaV=0.12.
The above fit yielded similar values, with estimates of r=0.35, K=2915, q=0.00042, and sigmaV=0.08.
let save =
Data.EstimationResult.saveAll (fun (d:DatingMethods.Annual) -> d.Value.ToString()) (__SOURCE_DIRECTORY__ + "cached/") "fisheries" "schaefer" (Some 200) r
We can estimate confidence intervals using a profile likelihood method. First, we load the cached result:
let resultCached =
Data.EstimationResult.loadAll
(fun s -> s |> Seq.map(fun s -> fst s, int (snd s) * 1<year> |> DatingMethods.Annual) |> TimeSeries.fromObservations DateMode.annualDateMode)
(__SOURCE_DIRECTORY__ + "cached/") "fisheries" Schaefer.model "schaefer"
|> Seq.head
Then, run the profiler:
let ci =
Bristlecone.Confidence.ProfileLikelihood.profile
Bristlecone.fit
engine
ts
Schaefer.model
10000
resultCached
The profile likelihood results indicate the following 95% confidence intervals:
- K: 2544 - 3255 (estimate = 2915)
- q: 0.00034 - 0.00053 (estimate = 0.00042)
- r: 0.30 - 0.42 (estimate = 0.35)
- sigmaV: 0.07 - 0.15 (estimate 0.08)
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> An F# Domain Specific Language (DSL) for scripting with Bristlecone. </summary>
val Measure: sId: MeasureId<'u> -> ModelExpression<'u>
--------------------
type MeasureAttribute = inherit Attribute new: unit -> MeasureAttribute
--------------------
new: unit -> MeasureAttribute
val parameter: code: string -> con: Parameter.Constraint<'u> -> lower: float<'u> -> upper: float<'u> -> IncludedParameter<'u>
<summary> Define an estimatable parameter for a Bristlecone model. </summary>
--------------------
[<Measure>] type parameter
<summary> For conditional statements, all three branches must have the same unit. </summary>
<summary>Negative log likelihood (-logL) functions to represent a variety of distributions and data types.</summary>
<namespacedoc><summary>Pre-built model parts for use in Bristlecone</summary></namespacedoc>
<summary> Terms for scaffolding a model system for use with Bristlecone. </summary>
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 with get member IsEmpty: bool with get member Item: index: int -> 'T with get ...
<summary> Contains types representing common dating methods in long term data analysis. </summary>
union case DatingMethods.Annual.Annual: int<year> -> 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 float: value: 'T -> float (requires member op_Explicit)
--------------------
type float = System.Double
--------------------
type float<'Measure> = float
The Schaefer model of fishing
module TimeSeries from Bristlecone.Time
<summary>Contains functions and types to create and manipulate `TimeSeries` values, which represent observations ordered in time.</summary>
--------------------
type TimeSeries<'T,'date,'timeunit,'timespan> = TimeSeries.TimeSeries<'T,'date,'timeunit,'timespan>
<summary>Arrange existing observations as a bristlecone `TimeSeries`. Observations become ordered and indexed by time.</summary>
<param name="dateType">A `DateMode` that handles the dating mode of choosing.</param>
<param name="dataset">A sequence of observations, which consist of data and dates / times</param>
<typeparam name="'T">The data type of the observations</typeparam>
<typeparam name="'date">The representaion of dates to use</typeparam>
<typeparam name="'dateUnit">The unit in which the dates are represented</typeparam>
<typeparam name="'timespan">The representation of timespans for `'date`</typeparam>
<returns>A time-series of observations ordered in time from oldest to newest.</returns>
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>
<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>
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
<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> Choose how the start point is chosen when solving the model system </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> Simple logger to console that prints line-by-line progress and events. </summary>
<summary> A simple console logger. `nIteration` specifies the number of iterations after which to log the current likelihood and parameter values. </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>Fit a time-series model to data.</summary>
<param name="engine">An estimation engine configured and tested for the given model.</param>
<param name="endCondition">The condition at which optimisation should cease.</param>
<param name="timeSeriesData">Time-series dataset that contains a series for each equation in the model system.</param>
<param name="model">A model system of equations, likelihood function, estimatible parameters, and optional measures.</param>
<returns>The result of the model-fitting procedure. If an error occurs, throws an exception.</returns>
<summary>Composable end conditions to specify when optimisation routines should end.</summary>
<summary> End on or after a minimum number of iterations. </summary>
namespace Bristlecone.Data
--------------------
namespace Microsoft.FSharp.Data
<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>
<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 Seq from Bristlecone
--------------------
module Seq from Microsoft.FSharp.Collections
<summary>Given a maximum likelihood estimate (MLE), the profile likelihood method runs a Monte Carlo algorithm that samples around the MLE.</summary>
<remarks>The range for each parameter is discovered at 95% and 68% confidence based on a chi squared distribution.</remarks>
<summary> The profile likelihood method samples the likelihood space around the Maximum Likelihood Estimate </summary>
bristlecone