Skip to content

Commit

Permalink
Merge pull request #14 from AndrewIOM/acm-fix-1
Browse files Browse the repository at this point in the history
More passing tests
  • Loading branch information
AndrewIOM authored Apr 15, 2024
2 parents 465fb9b + 43ff6d5 commit d939b1a
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 278 deletions.
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

0 comments on commit d939b1a

Please sign in to comment.