Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More passing tests #14

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 14 additions & 35 deletions samples/2-external-environment.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ let hypothesis =

let engine =
Bristlecone.mkContinuous
|> Bristlecone.withContinuousTime Integration.MathNet.integrate
|> Bristlecone.withConditioning Conditioning.RepeatFirstDataPoint
|> Bristlecone.withTunedMCMC [ Optimisation.MonteCarlo.TuneMethod.CovarianceWithScale 0.200, 250, Optimisation.EndConditions.afterIteration 20000 ]

Expand All @@ -68,11 +67,17 @@ let engine =
// 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 startValues = [ ShortCode.create "lynx", 30.09; ShortCode.create "hare", 19.58 ] |> Map.ofList
let testSettings =
Test.create
|> Test.withTimeSeriesLength 30
|> Test.addStartValues [ "stem radius", 2.3 ]
|> Test.addGenerationRules [
Test.GenerationRules.alwaysLessThan 1000. "stem radius"
Test.GenerationRules.alwaysMoreThan 0. "stem radius"
Test.GenerationRules.monotonicallyIncreasing "x" ] // There must be at least 10mm of wood production
|> Test.endWhen (Optimisation.EndConditions.afterIteration 1000)

// TODO Test settings new format

hypothesis |> Bristlecone.testModel engine Options.testSeriesLength startValues Options.iterations []
let testResult = Bristlecone.testModel engine testSettings hypothesis


// 4. Load Real Data
Expand All @@ -87,7 +92,6 @@ hypothesis |> Bristlecone.testModel engine Options.testSeriesLength startValues

open FSharp.Data

// TODO Is there a way to streamline this?
[<Literal>]
let DailyTemperatureUrl = __SOURCE_DIRECTORY__ + "/data/mean-temperature-daily.csv"

Expand All @@ -99,35 +103,10 @@ let meanTemperatureMonthly =
|> TimeSeries.interpolate
|> TimeSeries.generalise (FixedTemporalResolution.Months (PositiveInt.create 1)) (fun x -> x |> Seq.averageBy fst)


module Test =

open Bristlecone.Test

let settings = TestSettings.Default

let testSettings = {
Resolution = Years 1
TimeSeriesLength = 30
StartValues = [ code "b", 5.
code "t", 255. ] |> Map.ofList
EndCondition = Settings.endWhen
GenerationRules = [ "b" |> GenerationRules.alwaysLessThan 1000000.
"b" |> GenerationRules.alwaysMoreThan 0.
code "b", fun data -> (data |> Seq.max) - (data |> Seq.min) > 100. ]
NoiseGeneration = fun p data -> data
EnvironmentalData = [ code "t", TemperatureData.monthly ] |> Map.ofList
Random = MathNet.Numerics.Random.MersenneTwister()
StartDate = System.DateTime(1970,01,01)
Attempts = 50000 }

let run () =
hypothesis
|> Bristlecone.testModel Settings.engine testSettings


let testResult = Test.run()
// TODO read in stem radius sample dataset

// 4. Fit Model to Real Data
// -----------------------------------
let result = hypothesis |> Bristlecone.fit engine (Optimisation.EndConditions.afterIteration Options.iterations) data
let result =
hypothesis
|> Bristlecone.fit engine Settings.endWhen data
2 changes: 1 addition & 1 deletion samples/3-shrub-nitrogen.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ let testSettings =
Test.GenerationRules.monotonicallyIncreasing "x" ] // There must be at least 10mm of wood production
|> Test.addStartValues [
"x", 5.0
"bs", 5.0 |> Allometric.Proxies.toBiomassMM
"bs", 5.0<Dendro.mm> |> Allometric.Proxies.toBiomassMM
"N", 3.64 ]
|> Test.withTimeSeriesLength 30
|> Test.endWhen (Optimisation.EndConditions.afterIteration 1000)
Expand Down
5 changes: 2 additions & 3 deletions samples/bristlecone.fsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#r "../packages/FSharp.Data/lib/netstandard2.0/FSharp.Data.dll"
#r "../packages/MathNet.Numerics/lib/netstandard2.0/MathNet.Numerics.dll"
#r "../packages/MathNet.Numerics.FSharp/lib/netstandard2.0/MathNet.Numerics.FSharp.dll"
#r "nuget: MathNet.Numerics.FSharp"
#r "nuget: FSharp.Data"

#r "../src/Bristlecone/bin/Debug/netstandard2.0/Microsoft.Research.Oslo.dll"
#r "../src/Bristlecone/bin/Debug/netstandard2.0/Bristlecone.dll"
Expand Down
62 changes: 51 additions & 11 deletions src/Bristlecone/Language.fs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,39 @@ module Language =
| Invalid -> bind ((), "invalid model")
| Conditional _ -> bind ((), "conditional element") // TODO

type Requirement =
| ParameterRequirement of string
| EnvironmentRequirement of string

/// Determines the parameter and environmental data requirements of the defined model expression.
let rec requirements ex reqs =
match ex with
| This -> reqs
| Time -> reqs
| Environment name -> EnvironmentRequirement name :: reqs
| Parameter name -> ParameterRequirement name :: reqs
| Constant _ -> reqs
| Add list
| Multiply list ->
list
|> List.collect(fun l -> requirements l reqs)
|> List.append reqs
| Divide(l, r)
| Subtract(l, r) ->
[ requirements l reqs; requirements r reqs; reqs ] |> List.concat
| Arbitrary(fn, r) ->
r
|> List.map(fun r ->
match r with
| ArbitraryEnvironment e -> EnvironmentRequirement e
| ArbitraryParameter p -> ParameterRequirement p)
|> List.append reqs
| Mod(e, _) -> requirements e reqs
| Exponent(e, _) -> requirements e reqs
| Invalid -> reqs
| Conditional _ -> reqs


/// Allows common F# functions to use Bristlecone model expressions.
module ComputableFragment =

Expand Down Expand Up @@ -257,17 +290,24 @@ module Language =
|> Seq.choose id
|> Map.ofSeq

// let requirements =
// equations
// |> Map.map (fun k v -> ExpressionParser.describe v |> Writer.run |> snd)

// 1. Check all requirements are met (equations, measures, likelihood fn)

// 2. Check that all estimatable parameters are used.


// 3. Summarise each equation:
// > Print if mass / time is required.
if Seq.hasDuplicates (Seq.concat [ Map.keys measures; Map.keys equations ])
then failwith "Duplicate keys were used within equation and measures. These must be unique."

if equations.IsEmpty then failwith "No equations specified. You must state at least one model equation."

equations
|> Map.map (fun _ v -> ExpressionParser.requirements v [])
|> Map.toList
|> List.map snd
|> List.collect id
|> List.distinct
|> List.iter(fun req ->
match req with
| ExpressionParser.ParameterRequirement p ->
match parameters |> Parameter.Pool.hasParameter p with
| Some p -> ()
| None -> failwithf "The specified model requires the parameter '%s' but this has not been set up." p
| ExpressionParser.EnvironmentRequirement _ -> ())

{ Likelihood = likelihoods |> Seq.head
Parameters = parameters
Expand Down
11 changes: 11 additions & 0 deletions src/Bristlecone/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,21 @@ module Bristlecone =
// A. Setup initial time point values based on conditioning method.
let t0 = Fit.t0 timeSeriesData engine.Conditioning engine.LogTo

// Check there is time-series data actually included and corresponding to correct equations.
let hasRequiredData =
if timeSeriesData.IsEmpty then Error "No time-series data was specified"
else
if Set.isSubset (model.Equations |> Map.keys |> set) (timeSeriesData |> Map.keys |> set)
then Ok timeSeriesData
else Error (sprintf "Required time-series data were missing. Need: %A" (model.Equations |> Map.keys |> Seq.map (fun k -> k.Value) |> String.concat " + "))

// B. Create a continuous-time that outputs float[]
// containing only the values for the dynamic variable resolution.
let continuousSolver =
result {

let! timeSeriesData = hasRequiredData

// 1. Set time-series into common timeline
let! commonDynamicTimeFrame = Fit.observationsToCommonTimeFrame model.Equations timeSeriesData

Expand Down
3 changes: 3 additions & 0 deletions src/Bristlecone/Parameter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ module Parameter =

let toList pool = (pool |> unwrap) |> Map.toList

let hasParameter name pool =
pool |> unwrap |> Map.tryFindBy(fun k -> k.Value = name)

/// Returns Some value if a parameter with the `key`
/// exists in the Pool. The value returned is transformed
/// for an unconstrained parameter space.
Expand Down
3 changes: 3 additions & 0 deletions src/Bristlecone/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ module Seq =
let variance = nums |> List.averageBy (fun x -> sqr (x - mean))
sqrt (variance)

let hasDuplicates seq =
(seq |> Seq.distinct |> Seq.length) <> (seq |> Seq.length)

[<RequireQualifiedAccess>]
module Map =

Expand Down
1 change: 1 addition & 0 deletions tests/Bristlecone.Tests/Bristlecone.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Bristlecone/Bristlecone.fsproj" />
<Compile Include="Config.fs" />
<Compile Include="Time.fs" />
<Compile Include="Parameter.fs" />
<Compile Include="Optimisation.fs" />
Expand Down
Loading
Loading