Header menu logo bristlecone

ScriptNotebook

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:

Multiple items
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
module Language from Bristlecone
<summary> An F# Domain Specific Language (DSL) for scripting with Bristlecone. </summary>
module Time from Bristlecone
Multiple items
val Measure: sId: MeasureId<'u> -> ModelExpression<'u>

--------------------
type MeasureAttribute = inherit Attribute new: unit -> MeasureAttribute

--------------------
new: unit -> MeasureAttribute
[<Measure>] type kton
[<Measure>] type hour
val B: StateId<kton>
val state: name: string -> StateId<'u>
val I: MeasureId<1>
val measure: name: string -> MeasureId<'u>
val C: StateId<kton>
val environment: name: string -> StateId<'u>
val r: IncludedParameter</year>
Multiple items
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
val Positive: Parameter.Constraint<'u>
[<Measure>] type year
val K: IncludedParameter<kton>
val q: IncludedParameter</kton>
val B0: ModelExpression<kton>
val P: name: IncludedParameter<'u> -> ModelExpression<'u>
val dt: ModelExpression<year>
val Constant: x: float<'u> -> ModelExpression<'u>
val n: ModelExpression<kton>
val State: sId: StateId<'u> -> ModelExpression<'u>
val Environment: sid: StateId<'u> -> ModelExpression<'u>
val Conditional: cond: BoolExpression -> ifTrue: ModelExpression<'u> -> ifFalse: ModelExpression<'u> -> ModelExpression<'u>
<summary> For conditional statements, all three branches must have the same unit. </summary>
val It: ModelExpression<1>
val sigma: IncludedParameter<1>
val NoConstraints: Parameter.Constraint<'u>
val NLL: ModelSystem.Likelihood<ModelSystem.state>
namespace Bristlecone.ModelLibrary
module NegLogLikelihood from Bristlecone.ModelLibrary
<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>
val LogNormal: obs: Require.ObsForLikelihood<1> -> sigma: IncludedParameter<1> -> ModelSystem.Likelihood<'u>
module Require from Bristlecone.Language
val measure: m: MeasureId<'u> -> Require.ObsForLikelihood<'u>
val model: ModelSystem.ModelSystem<year>
module Model from Bristlecone.Language
<summary> Terms for scaffolding a model system for use with Bristlecone. </summary>
val discrete<'time> : ModelBuilder.ModelBuilder<'time>
val addDiscreteEquation: name: StateId<'state> -> expr: ModelExpression<'state> -> mb: ModelBuilder.ModelBuilder<'time> -> ModelBuilder.ModelBuilder<'time>
val addMeasure: name: MeasureId<'u> -> measure: ModelExpression<'u> -> builder: ModelBuilder.ModelBuilder<'time> -> ModelBuilder.ModelBuilder<'time>
val initialiseHiddenStateWith: name: StateId<'u> -> initialiser: ModelExpression<'u> -> builder: ModelBuilder.ModelBuilder<'time> -> ModelBuilder.ModelBuilder<'time>
val estimateParameter: p: IncludedParameter<'u> -> builder: ModelBuilder.ModelBuilder<'time> -> ModelBuilder.ModelBuilder<'time>
val useLikelihoodFunction: likelihoodFn: ModelSystem.Likelihood<ModelSystem.state> -> builder: ModelBuilder.ModelBuilder<'u> -> ModelBuilder.ModelBuilder<'u>
val compile: (ModelBuilder.ModelBuilder<'u> -> ModelSystem.ModelSystem<'u>)
val cpueData: (float * DatingMethods.Annual) list
val catchData: (float * DatingMethods.Annual) 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 with get member IsEmpty: bool with get member Item: index: int -> 'T with get ...
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val y: int
val cpue: float
val catch: int
val time: DatingMethods.Annual
module DatingMethods from Bristlecone.Time
<summary> Contains types representing common dating methods in long term data analysis. </summary>
Multiple items
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
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

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

--------------------
type float<'Measure> = float
val unzip: list: ('T1 * 'T2) list -> 'T1 list * 'T2 list
val ts: Map<ShortCode.ShortCode,TimeSeries.TimeSeries<float,DatingMethods.Annual,int<year>,int<year>>>
module Schaefer from Fisheries
 The Schaefer model of fishing
Multiple items
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>
val fromObservations<'T,'date,'dateUnit,'timespan (requires equality)> : dateType: DateMode.DateMode<'date,'dateUnit,'timespan> -> dataset: TimeSeries.Observation<'T,'date> seq -> TimeSeries.TimeSeries<'T,'date,'dateUnit,'timespan> (requires equality)
<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 DateMode from Bristlecone.Time
val annualDateMode: DateMode.DateMode<DatingMethods.Annual,int<year>,int<year>>
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 engine: EstimationEngine.EstimationEngine<DatingMethods.Annual,int<year>,year,1>
module EstimationEngine from 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>
type EstimationEngine<'date,'timespan,'modelTimeUnit,'state> = { TimeHandling: TimeMode OptimiseWith: Optimiser Conditioning: Conditioning<'state> LogTo: WriteOut ToModelTime: ResolutionToModelUnits<'date,'timespan,'modelTimeUnit> InterpolationGlobal: InterpolationMode InterpolationPerVariable: CodedMap<InterpolationMode> Random: Random }
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val mkDiscrete: unit -> EstimationEngine.EstimationEngine<System.DateTime,System.TimeSpan,year,'u>
<summary>A basic estimation engine for discrete-time equations, using a Nelder-Mead optimiser.</summary>
val withTimeConversion<'d,'d2,'timespan,'timespan2,'modelTimeUnit,'o1,'o2,'u> : fn: DateMode.Conversion.ResolutionToModelUnits<'d2,'timespan2,'modelTimeUnit> -> engine: EstimationEngine.EstimationEngine<'d,'o1,'o2,'u> -> EstimationEngine.EstimationEngine<'d2,'timespan2,'modelTimeUnit,'u>
module Conversion from Bristlecone.Time.DateMode
<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>
module Annual from Bristlecone.Time.DateMode.Conversion
val toYears: from: DateMode.Conversion.ConvertFrom<DatingMethods.Annual,int<year>> -> float<year>
val withConditioning: c: Conditioning.Conditioning<'u> -> engine: EstimationEngine.EstimationEngine<'a,'b,'v,'u> -> EstimationEngine.EstimationEngine<'a,'b,'v,'u>
<summary> Choose how the start point is chosen when solving the model system </summary>
module Conditioning from Bristlecone
union case Conditioning.Conditioning.NoConditioning: Conditioning.Conditioning<'u>
val withOutput: out: EstimationEngine.WriteOut -> engine: EstimationEngine.EstimationEngine<'a,'b,'u,'v> -> EstimationEngine.EstimationEngine<'a,'b,'u,'v>
<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>
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<iteration> -> (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>
[<Measure>] type iteration
val withCustomOptimisation: optim: EstimationEngine.Optimisation.Optimiser -> engine: EstimationEngine.EstimationEngine<'a,'b,'u,'v> -> EstimationEngine.EstimationEngine<'a,'b,'u,'v>
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>
val r: ModelSystem.EstimationResult<DatingMethods.Annual,int<year>,int<year>>
val fit: engine: EstimationEngine.EstimationEngine<'a,'b,'modelTimeUnit,'u> -> endCondition: EstimationEngine.EndCondition -> timeSeriesData: CodedMap<TimeSeries<float<'u>,'a,'c,'b>> -> model: ModelSystem.ModelSystem<'modelTimeUnit> -> ModelSystem.EstimationResult<'a,'c,'b> (requires comparison and comparison)
<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>
module EndConditions from Bristlecone.Optimisation
<summary>Composable end conditions to specify when optimisation routines should end.</summary>
val atIteration: iteration: int<iteration> -> EstimationEngine.Solution list -> currentIteration: int<iteration> -> EstimationEngine.OptimStopReason
<summary> End on or after a minimum number of iterations. </summary>
val save: unit
Multiple items
namespace Bristlecone.Data

--------------------
namespace Microsoft.FSharp.Data
module EstimationResult from Bristlecone.Data
val saveAll: dateToString: ('d -> string) -> directory: string -> subject: string -> modelId: string -> thinTraceBy: int option -> result: ModelSystem.EstimationResult<'d,'a,'b> -> 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>
val d: DatingMethods.Annual
property DatingMethods.Annual.Value: int<year> with get
System.ValueType.ToString() : string
union case Option.Some: Value: 'T -> Option<'T>
val resultCached: ModelSystem.EstimationResult<DatingMethods.Annual,int<year>,int<year>>
val loadAll: toSeries: ((ModelSystem.FitValue * string) seq -> ModelSystem.FitSeries<'a,'b,'c>) -> directory: string -> subject: string -> modelSystem: ModelSystem.ModelSystem<'modelTimeUnit> -> modelId: string -> ModelSystem.EstimationResult<'a,'b,'c> 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>
val s: (ModelSystem.FitValue * string) seq
Multiple items
module Seq from Bristlecone

--------------------
module Seq from Microsoft.FSharp.Collections
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val s: ModelSystem.FitValue * string
val fst: tuple: ('T1 * 'T2) -> 'T1
val snd: tuple: ('T1 * 'T2) -> 'T2
val head: source: 'T seq -> 'T
val ci: Map<ShortCode.ShortCode,Confidence.ConfidenceInterval>
namespace Bristlecone.Confidence
module ProfileLikelihood from Bristlecone.Confidence
<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>
val profile: fit: (EstimationEngine.EstimationEngine<'a,'b,'u,'v> -> EstimationEngine.EndCondition -> 'c -> ModelSystem.ModelSystem<'modelTimeUnit> -> ModelSystem.EstimationResult<'d,'e,'f>) -> engine: EstimationEngine.EstimationEngine<'a,'b,'u,'v> -> subject: 'c -> hypothesis: ModelSystem.ModelSystem<'modelTimeUnit> -> n: int -> result: ModelSystem.EstimationResult<'g,'h,'i> -> Map<ShortCode.ShortCode,Confidence.ConfidenceInterval>
<summary> The profile likelihood method samples the likelihood space around the Maximum Likelihood Estimate </summary>

Type something to start searching.