Skip to content

Commit

Permalink
Ability to change date modes in time-series types (#30)
Browse files Browse the repository at this point in the history
* [WIP] Old dates support in Time

* [WIP] no errors in time module

* [WIP] Refactoring for new time format

* [WIP] New time format compiles

* New time tests pass

* Fantomas formatted

* Time tests passing

* Benchmarks run again

* Annual mode and subsequently reworked dendro functions

* Sunrise tests

* Fix orchestrator with new timemodes

* Basic time-series docs
  • Loading branch information
AndrewIOM authored Oct 17, 2024
1 parent cec1b06 commit 01ae58b
Show file tree
Hide file tree
Showing 42 changed files with 1,742 additions and 727 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 Andrew Martin
Copyright (c) 2018 - 2024 Andrew Martin
OSLO Integrator Copyright (c) Microsoft Research
Amoeba Module Copyright (c) 2014 Mathias Brandewinder

Expand Down
4 changes: 2 additions & 2 deletions citest.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ open RProvider
open RProvider.``base``
open RProvider.zoo

R.c(1.,2.,3)
R.c (1., 2., 3)

R.as_zoo(R.c(1,2,3))
R.as_zoo (R.c (1, 2, 3))
2 changes: 1 addition & 1 deletion docs/data.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fun modelResults subjectIds hypothesisIds ->
|> Seq.map (fun (s, h, (a, b)) -> (s, h, a, b))
|> Bristlecone.Data.ModelSelection.save "/some/dir"

fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Hypotheses.Hypothesis> seq) ->
fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Hypotheses.Hypothesis<float>,_,_,_> seq) ->
hypothesisResults
|> ModelSelection.Akaike.akaikeWeightsForSet (fun h -> h.ReferenceCode)
|> Bristlecone.Data.ModelSelection.save "/some/dir"
Expand Down
6 changes: 3 additions & 3 deletions docs/dendro.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ location on any day of the year, we can use the `Sunrise.calculate` function.

open Bristlecone.Dendro

let latitude = 54.2
let longitude = -4.2
let latitude = 54.2<latitude>
let longitude = -4.2<longitude>

Sunrise.calculate 2024 04 20 latitude longitude "Europe/London"
(*** include-it ***)
Expand All @@ -61,5 +61,5 @@ Sunrise.calculate 2024 04 20 latitude longitude "Europe/London"
As an illustration, if we looked at Tromsø in January we would see far less available light:
*)

Sunrise.calculate 2024 01 10 69.64961 18.95702 "Europe/London"
Sunrise.calculate 2024 01 10 69.64961<latitude> 18.95702<longitude> "Europe/London"
(*** include-it ***)
12 changes: 6 additions & 6 deletions docs/examples/predator-prey.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ module Graphing =

open Plotly.NET

let pairedFits (series: Map<string, ModelSystem.FitSeries>) =
let pairedFits (series: Map<string, ModelSystem.FitSeries<_,_,_>>) =
match testResult with
| Ok r ->
series
Expand All @@ -177,17 +177,17 @@ module Graphing =
x
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let pairedFitsForTestResult (testResult: Result<Bristlecone.Test.TestResult, string>) =
let pairedFitsForTestResult (testResult: Result<Bristlecone.Test.TestResult<_,_,_>, string>) =
match testResult with
| Ok r -> pairedFits r.Series
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let pairedFitsForResult (testResult: Result<Bristlecone.ModelSystem.EstimationResult, string>) =
let pairedFitsForResult (testResult: Result<Bristlecone.ModelSystem.EstimationResult<_,_,_>, string>) =
match testResult with
| Ok r -> pairedFits (r.Series |> Seq.map (fun kv -> kv.Key.Value, kv.Value) |> Map.ofSeq)
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let parameterTrace (result: Result<ModelSystem.EstimationResult, 'b>) =
let parameterTrace (result: Result<ModelSystem.EstimationResult<_,_,_>, 'b>) =
match result with
| Ok r ->
r.Trace
Expand Down Expand Up @@ -223,8 +223,8 @@ type PopulationData = FSharp.Data.CsvProvider<"data/lynx-hare.csv", ResolutionFo
let data =
let csv = PopulationData.Load(__SOURCE_DIRECTORY__ + "/data/lynx-hare.csv")

[ (code "hare").Value, Time.TimeSeries.fromObservations (csv.Rows |> Seq.map (fun r -> float r.Hare, r.Year))
(code "lynx").Value, Time.TimeSeries.fromObservations (csv.Rows |> Seq.map (fun r -> float r.Lynx, r.Year)) ]
[ (code "hare").Value, Time.TimeSeries.fromNeoObservations (csv.Rows |> Seq.map (fun r -> float r.Hare, r.Year))
(code "lynx").Value, Time.TimeSeries.fromNeoObservations (csv.Rows |> Seq.map (fun r -> float r.Lynx, r.Year)) ]
|> Map.ofList

(*** include-value: data ***)
Expand Down
2 changes: 1 addition & 1 deletion docs/index.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ let engine =
Bristlecone.mkContinuous
|> Bristlecone.withCustomOptimisation (Optimisation.Amoeba.swarm 5 20 Optimisation.Amoeba.Solver.Default)

let testSettings = Bristlecone.Test.TestSettings<float>.Default
let testSettings = Bristlecone.Test.TestSettings<_,_,_,_>.Default
let testResult = Bristlecone.testModel engine testSettings hypothesis

(*** include-fsi-output ***)
Expand Down
6 changes: 3 additions & 3 deletions docs/integration.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ function to match the `EstimationEngine.Integration` type signature as follows:

open Bristlecone

let myCustomIntegrator: EstimationEngine.Integrate<'data, 'time> =
let myCustomIntegrator: EstimationEngine.Integrate<'data, 'time, _, _> =
fun writeOut tInitial tEnd tStep initialConditions externalEnvironment model ->
invalidOp "Doesn't actually do anything!"

let engine =
Bristlecone.mkContinuous |> Bristlecone.withContinuousTime myCustomIntegrator

Bristlecone.mkContinuous |> Bristlecone.withContinuousTime myCustomIntegrator

(**
Expand Down
2 changes: 1 addition & 1 deletion docs/model-selection.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ If you are working with `ResultSet`s, calculating and saving the weights is easi
be completed as below:
*)

fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Language.Hypotheses.Hypothesis> seq) ->
fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Language.Hypotheses.Hypothesis<float>, _, _, _> seq) ->
hypothesisResults
|> ModelSelection.Akaike.akaikeWeightsForSet (fun h -> h.ReferenceCode)
|> Bristlecone.Data.ModelSelection.save "/some/dir"
3 changes: 1 addition & 2 deletions docs/optimisation.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ let myCustomOptimiser: EstimationEngine.Optimiser<float> =
EstimationEngine.InDetachedSpace
<| fun writeOut n domain f -> invalidOp "Doesn't actually do anything!"

let engine =
Bristlecone.mkContinuous |> Bristlecone.withCustomOptimisation myCustomOptimiser
Bristlecone.mkContinuous |> Bristlecone.withCustomOptimisation myCustomOptimiser

(**
## Included optimisation methods
Expand Down
78 changes: 78 additions & 0 deletions docs/time-series.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
(**
---
title: Time-Series and Time-Frames
category: Components
categoryindex: 4
index: 1
---
[![Script]({{root}}/img/badge-script.svg)]({{root}}/{{fsdocs-source-basename}}.fsx)&emsp;
[![Notebook]({{root}}/img/badge-notebook.svg)]({{root}}/{{fsdocs-source-basename}}.ipynb)
*)

(*** condition: prepare ***)
#nowarn "211"
#r "../src/Bristlecone/bin/Debug/netstandard2.0/Bristlecone.dll"
(*** condition: fsx ***)
#if FSX
#r "nuget: Bristlecone,{{fsdocs-package-version}}"
#endif // FSX
(*** condition: ipynb ***)
#if IPYNB
#r "nuget: Bristlecone,{{fsdocs-package-version}}"
#endif // IPYNB

(**
Time-series and time-frames
========================
Bristlecone includes core representations of time-series,, time-frames, and
time indexes.
A key concept is the date mode. Bristlecone supports using different modes of measuring
time, from simple `DateTime` representation (calendar time) to various dating methods
commonly used in long-term ecology and archaeology.
The time-related types are included in the `Bristlecone.Time` module:
*)

open System
open Bristlecone
open Bristlecone.Time

(***
## Time Series
A time-series is a representation of data ordered in time by the date
of observation.
### From calendar-date observations
Time-series may be created from date-time observations using built-in .NET
types and the `TimeSeries.fromNeoObservations` function:
*)

let someObservations = [
2.1, DateTime(2020, 03, 21)
5.4, DateTime(2020, 03, 22)
-54.2, DateTime(2020, 03, 23)
]

let ts = TimeSeries.fromNeoObservations someObservations
(*** include-value: ts ***)

(**
### From radiocarbon dates
As an example of an alternative date format, uncalibrated radiocarbon dates
may be used as follows:
*)

let someDatedValues = [
1654, DatingMethods.Radiocarbon 345<``BP (radiocarbon)``>
982, DatingMethods.Radiocarbon -2<``BP (radiocarbon)``>
5433, DatingMethods.Radiocarbon 1023<``BP (radiocarbon)``>
]

let tsRadiocarbon = TimeSeries.fromObservations DateMode.radiocarbonDateMode someDatedValues
(*** include-value: tsRadiocarbon ***)
2 changes: 1 addition & 1 deletion docs/workflow.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ the orchestration agent:
let orchestrator =
Orchestration.OrchestrationAgent(logger, System.Environment.ProcessorCount, false)

fun datasets hypotheses engine ->
fun datasets hypotheses (engine:EstimationEngine.EstimationEngine<float,System.DateTime,System.TimeSpan,System.TimeSpan>) ->

// Orchestrate the analyses
let work = workPackages datasets hypotheses engine
Expand Down
93 changes: 53 additions & 40 deletions src/Bristlecone.Dendro/Data.fs
Original file line number Diff line number Diff line change
@@ -1,45 +1,58 @@
namespace Bristlecone.Data

open Bristlecone
open Bristlecone.Time
open Bristlecone.Dendro

module PlantIndividual =

open FSharp.Data

type RingWidthData = CsvProvider<"data-types/ring-width.csv">
type EnvironmentVariableData = CsvProvider<"data-types/env-variable.csv">

let loadRingWidths (fileName: string) =
let data = RingWidthData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
let growth =
open Bristlecone
open Bristlecone.Time
open Bristlecone.Dendro
open Bristlecone.Dendro.Units

/// Load plant-specific time-series from CSV files.
module Csv =

open FSharp.Data

type RingWidthData = CsvProvider<"data-types/ring-width.csv">
type EnvironmentVariableData = CsvProvider<"data-types/env-variable.csv">

/// <summary>Load ring widths from a CSV file that contains 'Year', 'Plant Code',
/// and 'Increment (mm)' columns.</summary>
/// <param name="csvFileName">The file to load</param>
/// <returns>A plant individual containing the growth series.</returns>
let loadRingWidths (csvFileName: string) =
let data = RingWidthData.Load csvFileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
let growth =
rows
|> Seq.sortBy (fun i -> i.Year)
|> Seq.map (fun i ->
(float i.``Increment (mm)`` * 1.<millimetre / year>, i.Year * 1<year> |> DatingMethods.Annual))
|> TimeSeries.fromObservations DateMode.annualDateMode
|> GrowthSeries.Absolute
|> PlantIndividual.PlantGrowth.RingWidth

{ Identifier = code |> ShortCode.create |> Option.get
Growth = growth
InternalControls = [] |> Map.ofList
Environment = [] |> Map.ofList }
: PlantIndividual.PlantIndividual<DatingMethods.Annual, int<year>, int<year>>)
|> Seq.toList

/// <summary>Load plant-specific environmental time-series from
/// a CSV file, where all time-series are specified in a single CSV.</summary>
/// <param name="fileName">The CSV file to load from.</param>
/// <returns>A map of time-series by their plant individual.</returns>
let loadPlantSpecificEnvironments (fileName: string) =
let data = EnvironmentVariableData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
code,
rows
|> Seq.sortBy (fun i -> i.Date)
|> Seq.map (fun i -> (float i.``Increment (mm)`` * 1.<mm>, i.Date))
|> TimeSeries.fromObservations
|> GrowthSeries.Absolute
|> PlantIndividual.RingWidth

{ Identifier = code |> ShortCode.create |> Option.get
Growth = growth
InternalControls = [] |> Map.ofList
Environment = [] |> Map.ofList }
: PlantIndividual.PlantIndividual)
|> Seq.toList

let loadLocalEnvironmentVariable (fileName: string) =
let data = EnvironmentVariableData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
code,
rows
|> Seq.map (fun i -> float i.Predictor, i.Date)
|> TimeSeries.fromObservations)
|> Seq.toList
|> Seq.map (fun i -> float i.Predictor, i.Year * 1<year> |> DatingMethods.Annual)
|> TimeSeries.fromObservations DateMode.annualDateMode)
|> Seq.toList
27 changes: 4 additions & 23 deletions src/Bristlecone.Dendro/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,14 @@ open Bristlecone.Dendro

module Bristlecone =

/// <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>
let fromDendro (plant: PlantIndividual.PlantIndividual) =
let g =
match plant.Growth |> PlantIndividual.growthSeries with
| GrowthSeries.Absolute g -> g
| GrowthSeries.Cumulative g -> g
| GrowthSeries.Relative g -> g

plant.Environment |> Map.add (ShortCode.create "x" |> Option.get) g

/// <summary>Fit a model to plant growth time-series. The plant individual's growth data is always labelled as `x`.</summary>
/// <param name="engine">A configured estimation engine</param>
/// <param name="endCondition">The end condition to apply to optimisation</param>
/// <param name="system">The compiled model system for analysis</param>
/// <param name="plant">A plant individual</param>
/// <returns>An estimation result for the given plant, model and fitting method (engine)</returns>
let fitDendro engine endCondition system (plant: PlantIndividual.PlantIndividual) =
Bristlecone.fit engine endCondition (fromDendro plant) system
let fitDendro engine endCondition system plant =
Bristlecone.fit engine endCondition (PlantIndividual.toTimeSeries plant) system

/// <summary> Perform n-step-ahead computation on the hypothesis and plant. The plant individual's growth data is always labelled as `x`.</summary>
/// <param name="engine"></param>
Expand All @@ -34,12 +21,6 @@ module Bristlecone =
/// <param name="preTransform"></param>
/// <param name="estimate"></param>
/// <returns></returns>
let oneStepAheadDendro
(engine: EstimationEngine.EstimationEngine<float, float>)
system
(plant: PlantIndividual.PlantIndividual)
preTransform
estimate
=
let predictors = fromDendro plant
let oneStepAheadDendro engine system plant preTransform estimate =
let predictors = PlantIndividual.toTimeSeries plant
Bristlecone.oneStepAhead engine system preTransform predictors estimate
Loading

0 comments on commit 01ae58b

Please sign in to comment.