Created
July 18, 2022 17:28
-
-
Save halcwb/eaad0977d962f17aaee78c18e6670a48 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#r "nuget: Expecto" | |
#r "nuget: Expecto.FsCheck" | |
#r "nuget: Unquote" | |
#r "../../Informedica.Utils.Lib/bin/Debug/net5.0/Informedica.Utils.Lib.dll" | |
#r "../../Informedica.GenUnits.Lib/bin/Debug/net5.0/Informedica.GenUnits.Lib.dll" | |
#time | |
/// Create the necessary test generators | |
module Generators = | |
open FsCheck | |
open MathNet.Numerics | |
open Expecto | |
let bigRGen (n, d) = | |
let bigRGen (n, d) = | |
let bigRGen (n, d) = | |
let d = if d = 0 then 1 else d | |
let n' = abs (n) |> BigRational.FromInt | |
let d' = abs (d) |> BigRational.FromInt | |
n' / d' | |
let bigRGenerator = | |
gen { | |
let! n = Arb.generate<int> | |
let! d = Arb.generate<int> | |
return bigRGen (n, d) | |
return bigRGen (n, d) | |
return bigRGen (n, d) | |
type BigRGenerator() = | |
type BigRGenerator() = | |
type BigRGenerator() = | |
static member BigRational() = | |
} | |
let config = | |
let config = | |
let config = | |
{ FsCheckConfig.defaultConfig with | |
arbitrary = [ typeof<BigRGenerator> ] | |
let testProp testName prop = | |
prop |> testPropertyWithConfig config testName | |
prop |> testPropertyWithConfig config testName | |
prop |> testPropertyWithConfig config testName | |
let run = | |
runTestsWithCLIArgs [] [| "--summary" |] | |
let run = | |
runTestsWithCLIArgs [] [| "--summary" |] | |
[<AutoOpen>] | |
module Utils = | |
/// Helper functions for `BigRational` | |
module BigRational = | |
module BigRational = | |
module BigRational = | |
/// ToDo: doesn't return `NoOp` but fails, | |
/// | |
/// | |
/// or subtraction, returns `NoOp` when | |
/// the operation is neither. | |
let (|Mult|Div|Add|Subtr|) op = | |
match op with | |
| _ when op |> BigRational.opIsMult -> Mult | |
| _ when op |> BigRational.opIsMult -> Mult | |
| _ when op |> BigRational.opIsMult -> Mult | |
| _ when op |> BigRational.opIsDiv -> Div | |
| _ when op |> BigRational.opIsAdd -> Add | |
let private toMultipleOf b d n = | |
let private toMultipleOf b d n = | |
let private toMultipleOf b d n = | |
if d = 0N then | |
n | |
else | |
let m = | |
(n / d) | |
|> BigRational.ToBigInt | |
|> BigRational.FromBigInt | |
(n / d) | |
if m * d < n then | |
(m + 1N) * d | |
else | |
m * d | |
else if m * d > n then | |
(m - 1N) * d | |
else | |
m * d | |
(n / d) | |
if m * d < n then | |
(m + 1N) * d | |
else | |
m * d | |
else if m * d > n then | |
(m - 1N) * d | |
m * d | |
if b then | |
|> Set.fold | |
(fun (b, acc) i -> | |
let ec = if isMax then (>=) else (<=) | |
let nc = if isMax then (>) else (<) | |
let ad = if isMax then (-) else (+) | |
let mult = | |
if isMax then | |
m |> toMaxMultipleOf i | |
else | |
m |> toMinMultipleOf i | |
let mult = | |
if (isIncl |> not) && (mult |> ec <| m) then | |
(mult |> ad <| i) | |
else | |
mult | |
else if m * d > n then | |
match acc with | |
| Some a -> | |
// need to preserve isIncl, i.e. `b` if nothing happened | |
if (mult |> nc <| a) then | |
(true, Some mult) | |
else | |
(b, Some a) | |
| None -> (true, Some mult) | |
) | |
(isIncl, None) | |
let mult = | |
if (isIncl |> not) && (mult |> ec <| m) then | |
let maxInclMultipleOf = | |
calcMinOrMaxToMultiple true true | |
else | |
mult | |
let maxExclMultipleOf = | |
calcMinOrMaxToMultiple true false | |
match acc with | |
| Some a -> | |
let minInclMultipleOf = | |
calcMinOrMaxToMultiple false true | |
if (mult |> nc <| a) then | |
(true, Some mult) | |
let minExclMultipleOf = | |
calcMinOrMaxToMultiple false false | |
(b, Some a) | |
| None -> (true, Some mult) | |
) | |
(isIncl, None) | |
let mult = | |
if (isIncl |> not) && (mult |> ec <| m) then | |
let maxInclMultipleOf = | |
calcMinOrMaxToMultiple true true | |
else | |
let tests = | |
testList | |
"bigrational" | |
[ | |
testList | |
"multiples" | |
[ | |
fun incr min -> | |
let mult = min |> toMinMultipleOf incr | |
if mult >= min then | |
true | |
else | |
printfn $"||{mult} >= {min}?||" | |
false | |
|> Generators.testProp | |
$"multiple of incr should be >= min" | |
fun incr min -> | |
let mult = | |
min |> minInclMultipleOf incr |> snd | |
if mult >= min then | |
true | |
else | |
printfn $"||{mult} >= {min}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be >= min incl" | |
let maxExclMultipleOf = | |
fun incr min -> | |
let mult = | |
min |> minExclMultipleOf incr |> snd | |
calcMinOrMaxToMultiple true false | |
if incr |> Set.count = 0 || mult > min then | |
true | |
else | |
printfn $"||{mult} > {min}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be > min excl" | |
fun incr max -> | |
let mult = max |> toMaxMultipleOf incr | |
(b, Some a) | |
if mult <= max then | |
true | |
else | |
printfn $"||{mult} <= {max}?||" | |
false | |
|> Generators.testProp | |
$"multiple of incr should be <= max" | |
calcMinOrMaxToMultiple true true | |
fun incr max -> | |
let mult = | |
max |> maxInclMultipleOf incr |> snd | |
testList | |
if mult <= max then | |
true | |
else | |
printfn $"||{mult} <= {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be <= max incl" | |
else | |
fun incr max -> | |
let mult = | |
max |> maxExclMultipleOf incr |> snd | |
min |> minInclMultipleOf incr |> snd | |
if incr |> Set.count = 0 || mult < max then | |
true | |
else | |
printfn $"||{mult} < {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be < max excl" | |
"multiple incrs should be >= min incl" | |
] | |
] | |
let mult = | |
let run () = tests |> Generators.run | |
true | |
else | |
printfn $"||{mult} > {min}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be > min excl" | |
fun incr max -> | |
let mult = max |> toMaxMultipleOf incr | |
if mult <= max then | |
true | |
else | |
printfn $"||{mult} <= {max}?||" | |
false | |
|> Generators.testProp | |
$"multiple of incr should be <= max" | |
fun incr max -> | |
let mult = | |
max |> maxInclMultipleOf incr |> snd | |
testList | |
if mult <= max then | |
xs |> List.map (fun a -> if pred a then x else a) | |
printfn $"||{mult} <= {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be <= max incl" | |
else | |
fun incr max -> | |
let mult = | |
max |> maxExclMultipleOf incr |> snd | |
min |> minInclMultipleOf incr |> snd | |
if incr |> Set.count = 0 || mult < max then | |
true | |
else | |
printfn $"||{mult} < {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be < max excl" | |
] | |
] | |
let mult = | |
let run () = tests |> Generators.run | |
true | |
else | |
printfn $"||{mult} > {min}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be > min excl" | |
let tests = | |
testList | |
"list" | |
[ | |
testList | |
"replace" | |
[ | |
fun xs -> | |
let isEven x = x % 2 = 0 | |
let even = xs |> replace isEven 2 | |
(xs |> List.filter isEven |> List.length) = (even | |
|> List | |
.filter ( | |
(=) | |
2 | |
) | |
|> List.length) | |
|> testProperty | |
"replace count should equel predicate count" | |
] | |
|> Generators.testProp | |
let run () = tests |> Generators.run | |
if mult <= max then | |
xs |> List.map (fun a -> if pred a then x else a) | |
printfn $"||{mult} <= {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be <= max incl" | |
fun incr max -> | |
let mult = | |
max |> maxExclMultipleOf incr |> snd | |
if incr |> Set.count = 0 || mult < max then | |
true | |
else | |
printfn $"||{mult} < {max}?||" | |
false | |
|> Generators.testProp | |
"multiple incrs should be < max excl" | |
] | |
] | |
let run () = tests |> Generators.run | |
/// Helper functions for `Option` | |
module Option = | |
let none _ = None | |
let tests = | |
testList | |
"list" | |
[ | |
testList | |
"replace" | |
[ | |
fun xs -> | |
let isEven x = x % 2 = 0 | |
let even = xs |> replace isEven 2 | |
(xs |> List.filter isEven |> List.length) = (even | |
|> List | |
.filter ( | |
(=) | |
2 | |
) | |
|> List.length) | |
|> testProperty | |
"replace count should equel predicate count" | |
] | |
/// Helper functions for `List` | |
let run () = tests |> Generators.run | |
/// when the `pred` function returns `true`. | |
let replace pred x xs = | |
xs |> List.map (fun a -> if pred a then x else a) | |
let distinct xs = | |
xs |> Seq.ofList |> Seq.distinct |> Seq.toList | |
/// Replace an element using a predicate | |
/// If element doesn't exist, add the element | |
let replaceOrAdd pred x xs = | |
if xs |> List.exists pred then | |
xs |> replace pred x | |
else | |
x :: xs | |
module Tests = | |
open Expecto | |
open Expecto.Flip | |
let testReplace () = | |
[ 1..10 ] |> replace (fun x -> x % 2 = 0) 0 | |
let testReplaceOrAdd () = [ 1..10 ] |> replaceOrAdd ((=) 11) 0 | |
[<Tests>] | |
let tests = | |
testList | |
"list" | |
[ | |
testList | |
"replace" | |
[ | |
fun xs -> | |
let isEven x = x % 2 = 0 | |
let even = xs |> replace isEven 2 | |
(xs |> List.filter isEven |> List.length) = (even | |
|> List | |
.filter ( | |
(=) | |
and Change = Change of change: Property * variable: Variable | |
) | |
|> List.length) | |
|> testProperty | |
"replace count should equel predicate count" | |
] | |
] | |
let run () = tests |> Generators.run | |
module Types = | |
open System | |
open MathNet.Numerics | |
/// Represents a non empty/null string identifying a `Variable`. | |
/// `Name` can be no longer than 1000 characters and cannot be | |
/// a null string | |
type Name = Name of string | |
/// The minimal value in | |
/// a `ValueRange`. Can be inclusive | |
/// or exclusive. | |
type Minimum = | |
| MinIncl of BigRational | |
| MinExcl of BigRational | |
/// The maximum value in | |
/// a `ValueRange`. Can be inclusive | |
/// or exclusive. | |
type Maximum = | |
| MaxIncl of BigRational | |
| MaxExcl of BigRational | |
type ValueSet = BigRational Set | |
type Increment = BigRational Set | |
/// `ValueRange` represents a domain of | |
/// rational numbers. A `ValueRange` can be either | |
/// - `Unrestricted`: any rational number | |
/// - `Increment`: any number that is a multiple of an increment | |
/// - `Min`: have a minimum | |
/// - `MinIncrement`: a minimum with the domain consisting of multiples of one increment | |
/// - `Max`: have a maximum | |
/// - `IncrementMax`: a domain of multiples of an increment with a maximum | |
/// - `MinMax`: have both a minimum and maximum | |
and Change = Change of change: Property * variable: Variable | |
type ValueRange = | |
| Unrestricted | |
| Min of Minimum | |
| Max of Maximum | |
| MinMax of Minimum * Maximum | |
| Incr of Increment | |
| MinIncr of min: Minimum * incr: Increment | |
| IncrMax of incr: Increment * max: Maximum | |
| ValueSet of ValueSet // Set<BigRational> | |
/// Represents a variable in an | |
/// `Equation`. The variable is | |
/// identified by `Name` and has | |
/// a `Values` described by the | |
/// `ValueRange`. | |
type Variable = { Name: Name; Values: ValueRange } | |
/// An equation is either a `ProductEquation` | |
/// or a `Sumequation`, the first variable is the | |
/// dependent variable, i.e. the result of the | |
/// equation, the second part are the independent | |
/// variables in the equation | |
type Equation = | |
| ProductEquation of Variable * Variable list | |
| SumEquation of Variable * Variable list | |
/// Represents a property of a `Variable`. | |
/// | |
/// - `MinIncl`: An inclusive minimum | |
/// - `MinExcl`: An exclusive minimum | |
/// - `MaxIncl`: An inclusive maximum | |
/// - `MaxExcl`: An exclusive maximum | |
/// - `RangeProp`: A delta with multiples | |
/// - `Vals`: A set of distinct values | |
type Property = | |
| IncrementProp of Increment | |
| MinInclProp of BigRational | |
| MinExclProp of BigRational | |
| MaxInclProp of BigRational | |
| MaxExclProp of BigRational | |
| ValsProp of BigRational Set | |
/// The `Result` of solving an `Equation` | |
/// is that either the `Equation` is the | |
/// same or has `Changed`. | |
type Result = | |
| UnChanged | |
| Changed of Change list | |
and Change = Change of change: Property * variable: Variable | |
/// A limitation of the maximum number | |
/// of values to use as a constraint | |
type Limit = | |
| MinLim of int | |
| MaxLim of int | |
| MinMaxLim of (int * int) | |
| NoLimit | |
/// Represents a constraint on a `Variable`. | |
/// I.e. either a set of values, or an increment | |
/// or a minimum of maximum. | |
type Constraint = | |
{ | |
Name: Name | |
Property: Property | |
Limit: Limit | |
} | |
module Events = | |
type Event = | |
| EquationCouldNotBeSolved of Equation | |
| EquationStartedCalculation of Variable list | |
| EquationStartedSolving of Equation | |
| EquationFinishedCalculation of Variable list * Variable list | |
| EquationVariableChanged of Variable | |
| EquationFinishedSolving of Variable list | |
| EquationLoopedSolving of | |
bool * | |
Variable * | |
Variable list * | |
Variable list | |
| SolverLoopedQue of Equation list | |
| ConstraintSortOrder of (int * Constraint) list | |
| ConstraintVariableNotFound of Constraint * Equation list | |
| ConstraintLimitSetToVariable of Limit * Variable | |
| ConstraintVariableApplied of Constraint * Variable | |
| ConstrainedEquationsSolved of Constraint * Equation list | |
| ApiSetVariable of Variable * Equation list | |
| ApiEquationsSolved of Equation list | |
| ApiAppliedConstraints of Constraint list * Equation list | |
module Exceptions = | |
type Message = | |
| NameNullOrWhiteSpaceException | |
| NameLongerThan1000 of string | |
| ValueRangeMinLargerThanMax of Minimum * Maximum | |
| ValueRangeNotAValidOperator | |
| ValueRangeEmptyValueSet | |
| VariableCannotSetValueRange of (Variable * ValueRange) | |
| EquationDuplicateVariables of Variable list | |
| EquationEmptyVariableList | |
| SolverInvalidEquations of Equation list | |
| SolverLooped of Equation list | |
module Logging = | |
type IMessage = | |
interface | |
end | |
type TimeStamp = DateTime | |
type Level = | |
| Informative | |
| Debug | |
let createExc = | |
create id Exceptions.raiseExc | |
| Error | |
type SolverMessage = | |
| ExceptionMessage of Exceptions.Message | |
| SolverMessage of Events.Event | |
interface IMessage | |
type Message = | |
{ | |
TimeStamp: TimeStamp | |
Level: Level | |
Message: IMessage | |
} | |
type Logger = { Log: Message -> unit } | |
module Logging = | |
open System | |
open Types.Logging | |
let private create l e = | |
{ | |
TimeStamp = DateTime.Now | |
Level = l | |
Message = e | |
} | |
let logMessage level (logger: Logger) evt = | |
evt |> SolverMessage |> create level |> logger.Log | |
let logInfo logger msg = logMessage Informative logger msg | |
let logWarning logger msg = logMessage Warning logger msg | |
let logError (logger: Logger) msg = | |
msg | |
let minGTEmin min1 min2 = min1 = min2 || minGTmin min1 min2 | |
|> logger.Log | |
let minSTmin min1 min2 = min2 |> minGTEmin min1 |> not | |
//// The notation for this is: | |
/// - <..> : meaning a variable can be any rational number | |
/// - [0N..> : meaning that the variable can be any number larger than or equal to 0N | |
/// - <0N..> : meaning that the variable can be any number larger than but excluding 0N | |
/// - [0N..10N> : meaning that the variable must be between 0N up to but excluding 10N | |
/// - [0N..10N] : meaning that the variable can be 0N up to and including 10N | |
/// - [1N,3N,4N,5N] : meaning that the variable can only be one of the numbers | |
/// | |
/// - `Name` : A `Variable` is identified by its `Name` | |
module Variable = | |
open System | |
open MathNet.Numerics | |
open Types | |
module Name = | |
open System | |
open Informedica.Utils.Lib.BCL | |
/// Eceptions that `Name` functions can raise | |
module Exceptions = | |
type Message = | |
| NullOrWhiteSpaceException | |
| LongerThan1000 of string | |
/// `ValueRange` exception type | |
exception NameException of Message | |
/// Raise a `ValueRangeException` with `Message` **m**. | |
let raiseExc m = m |> NameException |> raise | |
/// Create with continuation with **succ** function | |
/// when success and **fail** function when failure. | |
/// Creates a `Name` from a`string`. | |
let create succ fail s = | |
if s |> String.IsNullOrWhiteSpace then | |
Exceptions.NullOrWhiteSpaceException |> fail | |
else | |
match s |> String.trim with | |
let tests = | |
testList | |
"minimum" | |
[ | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 > m2 = (min1 |> minGTmin min2) | |
|> Generators.testProp "min1 > min2" | |
| n -> n |> Exceptions.LongerThan1000 |> fail | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 < min2" | |
/// an `NameException` when it fails. | |
fun m1 m2 -> | |
let min1 = createMin true m1 | |
let min2 = createMin false m2 | |
(m1 = m2 || m1 < m2) = (min1 |> minSTmin min2) | |
|> Generators.testProp | |
"min1 incl < min2 excl, also when min1 = min2" | |
fun m1 m2 -> | |
let min1 = createMin false m1 | |
let min2 = createMin true m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 excl < min2 incl" | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 >= m2 = (min1 |> minGTEmin min2) | |
|> Generators.testProp "min1 >= min2" | |
exception ValueRangeException of Exceptions.Message | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 <= m2 = (min1 |> minSTEmin min2) | |
|> Generators.testProp "min1 <= min2" | |
let raiseMinLargerThanMax min max = | |
fun m1 m2 -> | |
let min1 = createMin true m1 | |
let min2 = createMin false m2 | |
m1 > m2 = (min1 |> minGTmin min2) | |
|> Generators.testProp "min1 incl > min2 excl" | |
fun m1 m2 -> | |
let min1 = createMin false m1 | |
let min2 = createMin true m2 | |
(m1 = m2 || m1 > m2) = (min1 |> minGTmin min2) | |
|> Generators.testProp | |
"min1 excl > min2 incl, also when min1 = min2" | |
function | |
fun b m -> | |
let min = createMin b m | |
/// must be taken into account. | |
min | |
|> minToBoolBigRational | |
|> fun (b, m) -> createMin b m = min | |
|> Generators.testProp | |
"construct and deconstruct min there and back again" | |
] | |
fun b m1 m2 -> | |
let run () = tests |> Generators.run | |
|> Generators.testProp "min1 > min2" | |
| MinExcl m2, MinIncl m1 -> m2 >= m1 | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 < min2" | |
fun m1 m2 -> | |
let min1 = createMin true m1 | |
let min2 = createMin false m2 | |
(m1 = m2 || m1 < m2) = (min1 |> minSTmin min2) | |
|> Generators.testProp | |
"min1 incl < min2 excl, also when min1 = min2" | |
| MinIncl _ -> false | |
fun m1 m2 -> | |
let min1 = createMin false m1 | |
let min2 = createMin true m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 excl < min2 incl" | |
/// Creates a `Minimum` from a `BigRational` set. | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let maxGTEmax max1 max2 = max1 = max2 || maxGTmax max1 max2 | |
s |> Set.minElement |> MinIncl |> Some | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
let maxSTmax max1 max2 = max2 |> maxGTEmax max1 |> not | |
| MinExcl v -> v | |
fun m1 m2 -> | |
let min1 = createMin true m1 | |
let min2 = createMin false m2 | |
m1 > m2 = (min1 |> minGTmin min2) | |
|> Generators.testProp "min1 incl > min2 excl" | |
fun m1 m2 -> | |
let min1 = createMin false m1 | |
let min2 = createMin true m2 | |
(m1 = m2 || m1 > m2) = (min1 |> minGTmin min2) | |
|> Generators.testProp | |
"min1 excl > min2 incl, also when min1 = min2" | |
else | |
fun b m -> | |
let min = createMin b m | |
open Expecto | |
min | |
|> minToBoolBigRational | |
|> fun (b, m) -> createMin b m = min | |
|> Generators.testProp | |
"construct and deconstruct min there and back again" | |
] | |
fun b m1 m2 -> | |
let run () = tests |> Generators.run | |
|> Generators.testProp "min1 > min2" | |
fun b m1 m2 -> | |
let min1 = createMin b m1 | |
let min2 = createMin b m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 < min2" | |
fun m1 m2 -> | |
let min1 = createMin true m1 | |
let min2 = createMin false m2 | |
(m1 = m2 || m1 < m2) = (min1 |> minSTmin min2) | |
|> Generators.testProp | |
"min1 incl < min2 excl, also when min1 = min2" | |
fun m1 m2 -> | |
let min1 = createMin false m1 | |
let min2 = createMin true m2 | |
m1 < m2 = (min1 |> minSTmin min2) | |
|> Generators.testProp "min1 excl < min2 incl" | |
let tests = | |
testList | |
"maximum" | |
[ | |
fun b m1 m2 -> | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 > m2 = (max1 |> maxGTmax max2) | |
|> Generators.testProp "max1 > max2" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 < max2" | |
let min2 = createMin b m2 | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
(m1 = m2 || m1 < m2) = (max1 |> maxSTmax max2) | |
|> Generators.testProp | |
"max1 excl < max2 incl, also when max1 = max2" | |
m1 > m2 = (min1 |> minGTmin min2) | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
let max2 = createMax false m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 incl < max2 excl" | |
(m1 = m2 || m1 > m2) = (min1 |> minGTmin min2) | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 >= m2 = (max1 |> maxGTEmax max2) | |
|> Generators.testProp "max1 >= max2" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 <= m2 = (max1 |> maxSTEmax max2) | |
|> Generators.testProp "max1 <= max2" | |
] | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
m1 > m2 = (max1 |> maxGTmax max2) | |
|> Generators.testProp "max1 excl > max2 incl" | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
let max2 = createMax false m2 | |
(m1 = m2 || m1 > m2) = (max1 |> maxGTmax max2) | |
|> Generators.testProp | |
"max1 incl > max2 excl, also when max1 = max2" | |
function | |
fun b m -> | |
let max = createMax b m | |
/// must be taken into account. | |
max | |
|> maxToBoolBigRational | |
|> fun (b, m) -> createMax b m = max | |
|> Generators.testProp | |
"construct and deconstruct max there and back again" | |
"maximum" | |
] | |
fun b m1 m2 -> | |
let run () = tests |> Generators.run | |
|> Generators.testProp "max1 > max2" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 < max2" | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
(m1 = m2 || m1 < m2) = (max1 |> maxSTmax max2) | |
|> Generators.testProp | |
"max1 excl < max2 incl, also when max1 = max2" | |
if s |> Set.isEmpty then | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
let max2 = createMax false m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 incl < max2 excl" | |
let maxToBigRational = | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 >= m2 = (max1 |> maxGTEmax max2) | |
|> Generators.testProp "max1 >= max2" | |
let isMaxExcl = | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 <= m2 = (max1 |> maxSTEmax max2) | |
|> Generators.testProp "max1 <= max2" | |
let isMaxIncl = isMaxExcl >> not | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
m1 > m2 = (max1 |> maxGTmax max2) | |
|> Generators.testProp "max1 excl > max2 incl" | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
apply 0 zero zero zero zero zero zero Set.count | |
|> maxToBoolBigRational | |
|> fun (b, m) -> createMax b m = max | |
|> Generators.testProp | |
"construct and deconstruct max there and back again" | |
"maximum" | |
] | |
fun b m1 m2 -> | |
let run () = tests |> Generators.run | |
|> Generators.testProp "max1 > max2" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 < max2" | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
(m1 = m2 || m1 < m2) = (max1 |> maxSTmax max2) | |
|> Generators.testProp | |
"max1 excl < max2 incl, also when max1 = max2" | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
let max2 = createMax false m2 | |
m1 < m2 = (max1 |> maxSTmax max2) | |
|> Generators.testProp "max1 incl < max2 excl" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 >= m2 = (max1 |> maxGTEmax max2) | |
|> Generators.testProp "max1 >= max2" | |
fun b m1 m2 -> | |
let max1 = createMax b m1 | |
let max2 = createMax b m2 | |
m1 <= m2 = (max1 |> maxSTEmax max2) | |
|> Generators.testProp "max1 <= max2" | |
fun m1 m2 -> | |
let max1 = createMax false m1 | |
let max2 = createMax true m2 | |
m1 > m2 = (max1 |> maxGTmax max2) | |
|> Generators.testProp "max1 excl > max2 incl" | |
fun m1 m2 -> | |
let max1 = createMax true m1 | |
apply 0 zero zero zero zero zero zero Set.count | |
|> maxToBoolBigRational | |
|> fun (b, m) -> createMax b m = max | |
|> Generators.testProp | |
"construct and deconstruct max there and back again" | |
] | |
let run () = tests |> Generators.run | |
module ValueSet = | |
/// Create a `ValueSet` from a set of `BigRational`. | |
let create s = | |
if s |> Seq.isEmpty then | |
Exceptions.ValueRangeEmptyValueSet | |
|> Exceptions.raiseExc | |
else | |
s |> Set.ofSeq |> ValueSet | |
let getMin vs = | |
vs |> Set.minElement |> Minimum.createMin true | |
let getMax vs = | |
vs |> Set.maxElement |> Maximum.createMax true | |
/// Aply the give functions to `Values` | |
let apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fValueSet = | |
function | |
| Unrestricted -> unr | |
| Min min -> min |> fMin | |
| Max max -> max |> fMax | |
| MinMax (min, max) -> (min, max) |> fMinMax | |
| Incr incr -> incr |> fIncr | |
| MinIncr (min, incr) -> (min, incr) |> fMinIncr | |
| IncrMax (incr, max) -> (incr, max) |> fIncrMax | |
| ValueSet vs -> vs |> fValueSet | |
/// Count the number of values in a `ValueRange`. | |
/// Returns 0 if no count is possible. | |
let cardinality = | |
let zero _ = 0 | |
apply 0 zero zero zero zero zero zero Set.count | |
/// Checks whether a `ValueRange` is `Unrestricted` | |
let isUnrestricted = | |
let returnFalse = Boolean.returnFalse | |
apply | |
true | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
/// Checks whether a `ValueRange` is `Min` | |
let isMin = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
Boolean.returnTrue | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
/// Checks whether a `ValueRange` is `Max` | |
let isMax = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
returnFalse | |
Boolean.returnTrue | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
/// Checks whether a `ValueRange` is `MinMax` | |
let isMinMax = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
returnFalse | |
returnFalse | |
let filter minOpt incrOpt maxOpt = | |
returnFalse | |
v |> isBetweenMinMax minOpt maxOpt | |
&& v |> isMultipleOfIncr incrOpt | |
|> Set.filter | |
/// Checks whether a `ValueRange` is `Incr` | |
let isIncr = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
returnFalse | |
returnFalse | |
returnFalse | |
Boolean.returnTrue | |
returnFalse | |
returnFalse | |
returnFalse | |
/// Checks whether a `ValueRange` is `MinIncr` | |
let isMinIncr = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
returnFalse | |
returnFalse | |
returnFalse | |
returnFalse | |
Boolean.returnTrue | |
returnFalse | |
returnFalse | |
let incrToStr incr = | |
incr |> Seq.map brToStr |> String.concat ", " | |
/// Checks whether a `ValueRange` is `MinIncr` | |
let isIncrMax = | |
| Some min, None, None -> $"{left}{min |> brToStr}..>" | |
apply | |
| None, None, Some max -> $"<..{max |> brToStr}{right}" | |
returnFalse | |
returnFalse | |
$"{left}{min |> brToStr}..[{incr |> incrToStr}]..>" | |
returnFalse | |
Boolean.returnTrue | |
returnFalse | |
$"<..[{incr |> incrToStr}]..{max |> brToStr}{right}" | |
/// Checks whether a `ValueRange` is a `ValueSet` | |
let isValueSet = | |
let returnFalse = Boolean.returnFalse | |
apply | |
false | |
returnFalse | |
let filter minOpt incrOpt maxOpt = | |
returnFalse | |
v |> isBetweenMinMax minOpt maxOpt | |
&& v |> isMultipleOfIncr incrOpt | |
|> Set.filter | |
Boolean.returnTrue | |
/// Checks whether a `BigRational` is between an optional | |
/// **min** and an optional **max** | |
let incl, min = | |
min |> Minimum.minToBoolBigRational | |
let fMin = | |
function | |
| None -> true | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
| Some (Minimum.MinExcl m) -> v > m | |
let fMax = | |
let minincl, min = | |
min |> Minimum.minToBoolBigRational | |
let maxincl, max = | |
max |> Maximum.maxToBoolBigRational | |
| Some (Maximum.MaxIncl m) -> v <= m | |
| Some (Maximum.MaxExcl m) -> v < m | |
(fMin minOpt) && (fMax maxOpt) | |
let fMinIncr (min, incr) = | |
let incl, min = | |
min |> Minimum.minToBoolBigRational | |
let isMultipleOfIncr incrOpt v = | |
let isDiv v i = v |> BigRational.isMultiple i | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
| None -> true | |
/// Checks whether `Minimum` **min** > `Maximum` **max**. | |
let unr = | |
printRange None false None false None | |
vr | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fVs | |
/// accounted for. | |
let incrToStr incr = | |
incr |> Seq.map brToStr |> String.concat ", " | |
match min, max with | |
| Minimum.MinIncl mn, Maximum.MaxIncl mx -> mn > mx | |
| Some min, None, None -> $"{left}{min |> brToStr}..>" | |
| Minimum.MinExcl mn, Maximum.MaxExcl mx | |
| Minimum.MinIncl mn, Maximum.MaxExcl mx -> mn >= mx | |
| None, None, Some max -> $"<..{max |> brToStr}{right}" | |
/// Checks whether `Minimum` **min** <= `Maximum` **max** | |
let minSTEmax max min = min |> minLTmax max |> not | |
$"{left}{min |> brToStr}..[{incr |> incrToStr}]..>" | |
/// Checks whether `Minimum` **min** = `Maximum` **max**. | |
/// Note that when one or both minimum and maximum are exclusive, they | |
$"<..[{incr |> incrToStr}]..{max |> brToStr}{right}" | |
let minEQmax max min = | |
match min, max with | |
| Minimum.MinIncl mn, Maximum.MaxIncl mx -> mn = mx | |
| _ -> false | |
/// Filter a set of `BigRational` according | |
/// to **min** **max** and incr constraints | |
let filter minOpt incrOpt maxOpt = | |
fun v -> | |
v |> isBetweenMinMax minOpt maxOpt | |
&& v |> isMultipleOfIncr incrOpt | |
|> Set.filter | |
/// Create a string (to print) representation of a `ValueRange`. | |
/// `Exact` true prints exact bigrationals, when false | |
/// print as floating numbers | |
/// This assumes that the smallest increment will calculate the smallest | |
min |> Minimum.minToBoolBigRational | |
let printVals vals = | |
let vals = | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
|> List.sort | |
|> List.map ( | |
if exact then | |
let minincl, min = | |
min |> Minimum.minToBoolBigRational | |
let maxincl, max = | |
max |> Maximum.maxToBoolBigRational | |
BigRational.toFloat >> sprintf "%A" | |
) | |
$"""[{vals |> String.concat ", "}]""" | |
let fMinIncr (min, incr) = | |
let incl, min = | |
min |> Minimum.minToBoolBigRational | |
let min = | |
min | |
|> minMultipleOf incr | |
|> Minimum.minToBigRational | |
let max = | |
max | |
|> maxMultipleOf incr | |
|> Maximum.maxToBigRational | |
else | |
let left = if minincl then "[" else "<" | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
if exact then | |
else | |
let unr = | |
printRange None false None false None | |
vr | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fVs | |
let incrToStr incr = | |
incr |> Seq.map brToStr |> String.concat ", " | |
match min, incr, max with | |
| Some min, None, None -> $"{left}{min |> brToStr}..>" | |
| Some min, None, Some max -> | |
$"{left}{min |> brToStr}..{max |> brToStr}{right}" | |
| None, None, Some max -> $"<..{max |> brToStr}{right}" | |
| None, Some incr, None -> $"<..[{incr |> incrToStr}]..>" | |
| Some min, Some incr, None -> | |
(min |> minMultipleOf incr, incr) |> MinIncr | |
| Some min, Some incr, Some max -> | |
$"{left}{min |> brToStr}..[{incr |> incrToStr}]..{max |> brToStr}{right}" | |
| None, Some incr, Some max -> | |
$"<..[{incr |> incrToStr}]..{max |> brToStr}{right}" | |
| None, None, None -> "[]" | |
if vals |> List.isEmpty |> not then | |
vals |> printVals | |
else | |
printRange min max incr | |
/// Convert a `ValueRange` to a `string`. | |
let toString exact vr = | |
let fVs vs = | |
print exact false None false None false None (vs |> Set.toList) | |
let printRange min minincl max maxincl incr = | |
print exact false min minincl max maxincl incr [] | |
let fMin min = | |
/// This assumes that the smallest increment will calculate the smallest | |
min |> Minimum.minToBoolBigRational | |
printRange (Some min) incl None false None | |
let fMax max = | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
printRange None false (Some max) incl None | |
let fMinMax (min, max) = | |
let minincl, min = | |
min |> Minimum.minToBoolBigRational | |
let maxincl, max = | |
max |> Maximum.maxToBoolBigRational | |
printRange (Some min) minincl (Some max) maxincl None | |
let fIncr incr = | |
printRange None false None false (Some incr) | |
let fMinIncr (min, incr) = | |
let incl, min = | |
min |> Minimum.minToBoolBigRational | |
let min = | |
min | |
|> minMultipleOf incr | |
|> Minimum.minToBigRational | |
let max = | |
max | |
|> maxMultipleOf incr | |
|> Maximum.maxToBigRational | |
let fIncrMax (incr, max) = | |
let incl, max = | |
max |> Maximum.maxToBoolBigRational | |
printRange None false (Some max) incl (Some incr) | |
let unr = | |
printRange None false None false None | |
vr | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fVs | |
v |> isBetweenMinMax min max | |
&& v |> isMultipleOfIncr incr | |
let unrestricted = Unrestricted | |
/// Create a `Minimum` `Range` that is | |
/// either inclusive or exclusive. | |
let createMinValueRange isIncl m = m |> Minimum.createMin isIncl |> Min | |
(min |> minMultipleOf incr, incr) |> MinIncr | |
/// either inclusive or exclusive. | |
let createMaxValueRange isIncl m = m |> Maximum.createMax isIncl |> Max | |
/// Create a `MinMax` `ValueRange`. If **min** > **max** raises | |
/// an `MinLargetThanMax` exception. If min equals max, a `ValueSet` with | |
/// value min (= max). | |
let minMaxToValueRange min max = | |
if min |> minLTmax max then | |
Exceptions.raiseMinLargerThanMax min max | |
elif min |> minEQmax max then | |
minIncrToValueRange (newMin |> minMultipleOf incr) incr | |
|> Minimum.minToBigRational | |
|> Set.singleton | |
|> ValueSet.create | |
else | |
(min, max) |> MinMax | |
/// Calculate `Minimum` as a multiple of `Increment` **incr**. | |
/// This assumes that the smallest increment will calculate the smallest | |
/// minimum as a multiple of that increment. | |
let minMultipleOf incr min = | |
match min |> Minimum.minToBoolBigRational with | |
| true, br -> br |> BigRational.minInclMultipleOf incr | |
| false, br -> br |> BigRational.minExclMultipleOf incr | |
|> fun (b, br) -> Minimum.createMin b br | |
// Calculate `Maximum` **max** as a multiple of **incr**. | |
let maxMultipleOf incr max = | |
match max |> Maximum.maxToBoolBigRational with | |
| true, br -> br |> BigRational.maxInclMultipleOf incr | |
| false, br -> br |> BigRational.maxExclMultipleOf incr | |
|> fun (b, br) -> Maximum.createMax b br | |
let minIncrToValueRange min incr = | |
(min |> minMultipleOf incr, incr) |> MinIncr | |
let incrMaxToValueRange incr max = | |
(incr, max |> maxMultipleOf incr) |> IncrMax | |
/// Create a set of `BigRational` using **min**, **incr** and a **max**. | |
let minIncrMaxToValueRange min incr max = | |
let min = | |
min | |
|> minMultipleOf incr | |
|> Minimum.minToBigRational | |
let max = | |
max | |
|> maxMultipleOf incr | |
|> Maximum.maxToBigRational | |
let fMinIncr (min, incr) = minIncrMaxToValueRange min incr newMax | |
for i in incr do | |
[ min..i..max ] | |
] | |
|> List.collect id | |
|> Set.ofList | |
|> ValueSet | |
/// Create a `ValueRange` using a `ValueSet` **vs** | |
/// an optional `Minimum` **min** and `Maximum` **max**. | |
/// If both **min** and **max** are `None` an `Unrestricted` | |
/// `ValueRange` is created. | |
v |> isBetweenMinMax min max | |
&& v |> isMultipleOfIncr incr | |
| None -> | |
match minOpt, incrOpt, maxOpt with | |
| None, None, None -> unrestricted | |
| Some min, None, None -> min |> Min | |
| None, None, Some max -> max |> Max | |
| Some min, None, Some max -> minMaxToValueRange min max | |
| None, Some incr, None -> incr |> Incr | |
| Some min, Some incr, None -> | |
(min |> minMultipleOf incr, incr) |> MinIncr | |
| None, Some incr, Some max -> | |
(incr, max |> maxMultipleOf incr) |> IncrMax | |
| Some min, Some incr, Some max -> | |
minIncrMaxToValueRange min incr max | |
| Some vs -> vs |> ValueSet.create | |
/// Get an optional `Minimum` in a `ValueRange` | |
incr | |
|> Set.exists (fun i' -> i |> BigRational.isMultiple i') | |
) then | |
newIncr | |
minIncrToValueRange (newMin |> minMultipleOf incr) incr | |
else | |
incr | |
Option.none | |
(fst >> Some) | |
Option.none | |
(ValueSet.getMin >> Some) | |
let fMax max = | |
(newIncr, max |> maxMultipleOf newIncr) |> IncrMax | |
/// Get an optional `Maximum` in a `ValueRange` | |
let fMinMax (min, max) = minIncrMaxToValueRange min newIncr max | |
None | |
Option.none | |
Some | |
(snd >> Some) | |
Option.none | |
Option.none | |
(snd >> Some) | |
(ValueSet.getMax >> Some) | |
/// Get an optional `Incr` in a `ValueRange` | |
let getIncr = | |
apply | |
None | |
Option.none | |
Option.none | |
Option.none | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fValueSet | |
let getValueSet = | |
apply | |
None | |
Option.none | |
Option.none | |
Option.none | |
Option.none | |
Option.none | |
vr |> getMin, vr |> getIncr, vr |> getMax, vr |> getValueSet | |
/// a `BigRational` **v**. | |
let contains v vr = | |
match vr with | |
| ValueSet vs -> vs |> Set.contains v | |
| _ -> | |
let min = vr |> getMin | |
let max = vr |> getMax | |
let incr = vr |> getIncr | |
v |> isBetweenMinMax min max | |
&& v |> isMultipleOfIncr incr | |
/// Apply a `Minimum` **min** to a `ValueRange` **vr**. | |
/// If minimum cannot be set the original `Minimum` is returned. | |
/// So, it always returns a more restrictive, i.e. larger, or equal `Minimum`. | |
let setMin newMin (vr: ValueRange) = | |
// Check whether the new min is more restrictive than the old min | |
let checkMin min = | |
if newMin |> Minimum.minGTmin min then | |
newMin | |
else | |
min | |
let raiseExc = | |
MinMaxCalculatorException >> raise | |
let fMin = checkMin >> Min | |
let fMax max = minMaxToValueRange newMin max | |
incr | |
|> Set.exists (fun i' -> i |> BigRational.isMultiple i') | |
) then | |
newIncr | |
minIncrToValueRange (newMin |> minMultipleOf incr) incr | |
else | |
incr | |
let fMinIncr (min, incr) = | |
minIncrToValueRange (min |> checkMin) incr | |
let fIncrMax (incr, max) = | |
minIncrMaxToValueRange (newMin |> checkMin) incr max | |
let fMax max = | |
(newIncr, max |> maxMultipleOf newIncr) |> IncrMax | |
let fValueSet = | |
let fMinMax (min, max) = minIncrMaxToValueRange min newIncr max | |
filter (Some newMin) incr max >> ValueSet.create | |
vr | |
|> apply | |
(newMin |> Min) | |
fMin | |
fMax | |
fMinMax | |
fIncr | |
fMinIncr | |
fIncrMax | |
fValueSet | |
/// Apply a `Maximum` **max** to a `ValueRange` **vr**. | |
/// If maximum cannot be set the original is returned. | |
/// So, it always returns a more restrictive, i.e. smaller, or equal `Maximum`. | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fValueSet | |
let fMax = checkMax >> Max | |
let fMinMax (min, max) = | |
minMaxToValueRange min (max |> checkMax) | |
let fIncr incr = incrMaxToValueRange incr newMax | |
vr |> getMin, vr |> getIncr, vr |> getMax, vr |> getValueSet | |
let fValueSet = | |
let min = vr |> getMin | |
let incr = vr |> getIncr | |
filter min incr (Some newMax) >> ValueSet.create | |
vr | |
|> apply | |
(newMax |> Max) | |
fMin | |
fMax | |
fMinMax | |
fIncr | |
fMinIncr | |
fIncrMax | |
fValueSet | |
/// Apply a **incr** to a `ValueRange` **vr**. | |
/// If increment cannot be set the original is returned. | |
/// So, the resulting increment is always more restrictive as the previous one | |
let setIncr newIncr vr = | |
let raiseExc = | |
MinMaxCalculatorException >> raise | |
// ToDo needs testing!! | |
let checkIncr incr = | |
if newIncr | |
|> Set.forall (fun i -> | |
incr | |
|> Set.exists (fun i' -> i |> BigRational.isMultiple i') | |
) then | |
newIncr | |
else | |
incr | |
let unr = newIncr |> Incr | |
let fMin min = minIncrToValueRange min newIncr | |
let fMax max = | |
(newIncr, max |> maxMultipleOf newIncr) |> IncrMax | |
let fMinMax (min, max) = minIncrMaxToValueRange min newIncr max | |
let fIncr incr = incr |> checkIncr |> Incr | |
let fMinIncr (min, incr) = | |
let incr = incr |> checkIncr | |
(min |> minMultipleOf incr, incr) |> MinIncr | |
let fIncrMax (incr, max) = | |
let incr = incr |> checkIncr | |
(incr, max |> maxMultipleOf incr) |> IncrMax | |
let fValueSet = | |
let min = vr |> getMin | |
let max = vr |> getMax | |
filter min (Some newIncr) max >> ValueSet.create | |
vr | |
|> apply unr fMin fMax fMinMax fIncr fMinIncr fIncrMax fValueSet | |
/// Appy a set of `BigRational` to a `ValueRange` **vr**. | |
/// the result is a filtered or the intersect of | |
/// the set of `BigRational` and **vr**. | |
let setValues newVs vr = | |
let min, incr, max, oldVs = | |
vr |> getMin, vr |> getIncr, vr |> getMax, vr |> getValueSet | |
let vs = | |
match oldVs with | |
| None -> newVs |> filter min incr max | |
| Some vs -> newVs |> filter min incr max |> Set.intersect vs | |
create min incr max (Some vs) | |
/// Functions to calculate the `Minimum` | |
/// and `Maximum` in a `ValueRange`. | |
/// I.e. what happens when you mult, div, add or subtr | |
/// a `Range`, for example: | |
/// <1N..3N> * <4N..5N> = <4N..15N> | |
module MinMaxCalcultor = | |
/// Exceptions that a MinMaxCalculator function can raise. | |
module Exceptions = | |
type Message = | NotAValidOperator | |
exception MinMaxCalculatorException of Message | |
let raiseExc = | |
MinMaxCalculatorException >> raise | |
| BigRational.Add | |
| BigRational.Subtr -> | |
/// Calculate **x1** and **x2** with operator **op** | |
/// and use **incl1** and **inc2** to determine whether | |
/// the result is inclusive. Use constructor **c** to | |
/// create the optional result. | |
let calc c op (x1, incl1) (x2, incl2) = | |
let incl = | |
match incl1, incl2 with | |
// incr cannot be calculated based on division | |
| _ -> false | |
match x1, x2 with | |
| Some (v1), Some (v2) -> | |
if op |> BigRational.opIsDiv && v2 = 0N then | |
None | |
else | |
v1 |> op <| v2 |> c incl |> Some | |
| _ -> None | |
/// Calculate an optional `Minimum` | |
let calcMin = calc Minimum.createMin | |
/// Calculate an optional `Maximum` | |
let calcMax = calc Maximum.createMax | |
/// Match a min, max tuple **min**, **max** | |
/// to: | |
/// | |
/// * `PP`: both positive | |
/// * `NN`: both negative | |
/// * `NP`: one negative, the other positive | |
let (|PP|NN|NP|) (min, max) = | |
match min, max with | |
| Some (min), _ when min >= 0N -> PP | |
| _, Some (max) when max < 0N -> NN | |
| ValueSet s, MinIncr (_, i) | |
| MinIncr (_, i), ValueSet s | |
| ValueSet s, IncrMax (i, _) | |
| IncrMax (i, _), ValueSet s | |
/// `Maximum` option for addition of | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s | |
let min = calcMin (+) min1 min2 | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s -> | |
/// Calculate `Minimum` option and | |
/// `Maximum` option for subtraction of | |
/// (**min1**, **max1**) and (**min2**, **max2) | |
let subtraction min1 max1 min2 max2 = | |
let min = calcMin (-) min1 max2 | |
let max = calcMax (-) max1 min2 | |
min, max | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
/// Calculate `Minimum` option and | |
/// `Maximum` option for multiplication of | |
/// (**min1**, **max1**) and (**min2**, **max2) | |
let multiplication min1 max1 min2 max2 = | |
match ((min1 |> fst), (max1 |> fst)), | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
| PP, PP -> // min = min1 * min2, max = max1 * max2 | |
calcMin (*) min1 min2, calcMax (*) max1 max2 | |
| PP, NN -> // min = max1 * min2, max = min1 * max2 | |
calcMin (*) max1 min2, calcMax (*) min1 max2 | |
| PP, NP -> // min = min1 * min2, max = max1 * max2 | |
| BigRational.Add | |
| BigRational.Subtr -> | |
| NN, PP -> // min = min1 * max2, max = max1 * min2 | |
calcMin (*) min1 max2, calcMax (*) max1 min2 | |
| NN, NN -> // min = max1 * max2, max = min1 * min2 | |
calcMin (*) max1 max2, calcMax (*) min1 min2 | |
| NN, NP -> // min = min1 * max2, max = min1 * min2 | |
calcMin (*) min1 max2, calcMax (*) min1 min2 | |
| NP, PP -> // min = min1 * max2, max = max1 * max2 | |
// incr cannot be calculated based on division | |
| NP, NN -> // min = max1 * min2, max = min1 * min2 | |
calcMin (*) max1 min2, calcMax (*) min1 min2 | |
| NP, NP -> // min = min1 * max2, max = max1 * max2 | |
calcMin (*) min1 max2, calcMax (*) max1 max2 | |
let min1, incr1, max1 = | |
x1 |> getMin, x1 |> getIncr, x1 |> getMax | |
let min2, incr2, max2 = | |
x2 |> getMin, x2 |> getIncr, x2 |> getMax | |
/// Calculate `Minimum` option and | |
/// `Maximum` option for division of | |
/// (**min1**, **max1**) and (**min2**, **max2) | |
let division min1 max1 min2 max2 = | |
match (min1 |> fst, max1 |> fst), (min2 |> fst, max2 |> fst) | |
with | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
| PP, NN -> // min = max1 / max2 , max = min1 / min2 | |
calcMin (/) max1 max2, calcMax (/) min1 min2 | |
| NN, PP -> // min = min1 / min2, max = max1 / max2 | |
calcMin (/) min1 min2, calcMax (/) max1 max2 | |
| NN, NN -> // min = max1 / min2 , max = min1 / max2 | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
calcMin (/) min1 min2, calcMax (/) max1 min2 | |
| NP, NN -> // min = max1 / max2, max = min1 / max2 | |
calcMin (/) max1 max2, calcMax (/) min1 max2 | |
// division by range containing zero | |
| NN, NP | |
| PP, NP | |
| NP, NP -> None, None | |
| ValueSet s, MinIncr (_, i) | |
| MinIncr (_, i), ValueSet s | |
/// according to the operand | |
| ValueSet s, IncrMax (i, _) | |
| IncrMax (i, _), ValueSet s | |
| BigRational.Mult -> multiplication | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s | |
| BigRational.Subtr -> subtraction | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s -> | |
|> Exceptions.raiseExc | |
/// Calculate an increment with | |
/// **incr1** of x1 and **incr2** of x2 | |
/// in an equation: y = x1 **op** x2 | |
let calcIncr op incr1 incr2 = | |
match incr1, incr2 with | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
// y.incr = x1.incr * x2.incr | |
| BigRational.Mult -> | |
[ | |
for x in i1 do | |
for y in i2 do | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
] | |
|> Set.ofList | |
|> Some | |
// when y = x1 + x2 then y.incr = gcd of x1.incr and x2.incr | |
| BigRational.Add | |
| BigRational.Subtr -> | |
[ | |
for x in i1 do | |
for y in i2 do | |
BigRational.gcd x y | |
] | |
|> Set.ofList | |
|> Some | |
// incr cannot be calculated based on division | |
| _ -> None | |
| _ -> None | |
let min1, incr1, max1 = | |
x1 |> getMin, x1 |> getIncr, x1 |> getMax | |
let min2, incr2, max2 = | |
x2 |> getMin, x2 |> getIncr, x2 |> getMax | |
/// to `ValueRange` **x1** and **x2**. | |
/// Calculates `Minimum`, increment or `Maximum` | |
/// if either **x1** or **x2** is not a `ValueSet`. | |
/// Doesn't perform any calculation when both | |
/// **x1** and **x2** are `Unrestricted`. | |
let calc op (x1, x2) = | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
| Unrestricted, Unrestricted -> unrestricted | |
| ValueSet s1, ValueSet s2 -> | |
// When one of the sets does not contain any value then the result of | |
// of the calculation cannot contain any value either | |
if s1 |> Set.isEmpty || s2 |> Set.isEmpty then | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
else | |
Seq.allPairs s1 s2 | |
|> Seq.map (fun (x1, x2) -> x1 |> op <| x2) | |
|> ValueSet.create | |
// A set with an increment results in a new set of increment | |
// Need to match all scenarios with a valueset and an increment | |
| ValueSet s, MinIncr (_, i) | |
| MinIncr (_, i), ValueSet s | |
| ValueSet s, IncrMax (i, _) | |
| IncrMax (i, _), ValueSet s | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s | |
| ValueSet s, MinIncr (_, i) | |
| IncrMax (i, _), ValueSet s -> | |
let createRes = | |
createSucc ("Result" |> Name.createExc) | |
let min1, max1 = x1 |> getMin, x1 |> getMax | |
let min2, max2 = x2 |> getMin, x2 |> getMax | |
let min, max = | |
let getMin m = | |
let incl = | |
match m with | |
| Some v -> v |> Minimum.isMinIncl | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
let getMax m = | |
let incl = | |
match m with | |
| Some v -> v |> Maximum.isMaxIncl | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
MinMaxCalcultor.calcMinMax | |
op | |
(min1 |> getMin) | |
(max1 |> getMax) | |
(min2 |> getMin) | |
(max2 |> getMax) | |
// calculate a new increment based upon the valueset and an increment | |
let incr1 = i |> Some | |
let incr2 = s |> Some | |
let incr = calcIncr op incr1 incr2 | |
match min, incr, max with | |
| None, None, None -> unrestricted | |
| _ -> create min incr max None | |
// In any other case calculate min, incr and max | |
| _ -> | |
let min1, incr1, max1 = | |
x1 |> getMin, x1 |> getIncr, x1 |> getMax | |
let min2, incr2, max2 = | |
x2 |> getMin, x2 |> getIncr, x2 |> getMax | |
let min, max = | |
let getMin m = | |
let incl = | |
match m with | |
| Some v -> v |> Minimum.isMinIncl | |
| None -> false | |
m | |
|> Option.bind (Minimum.minToBigRational >> Some), | |
incl | |
let count v = | |
v |> getValueRange |> ValueRange.cardinality | |
let getMax m = | |
let incl = | |
match m with | |
| Some v -> v |> Maximum.isMaxIncl | |
| None -> false | |
m | |
|> Option.bind (Maximum.maxToBigRational >> Some), | |
incl | |
MinMaxCalcultor.calcMinMax | |
op | |
(min1 |> getMin) | |
(max1 |> getMax) | |
(min2 |> getMin) | |
(max2 |> getMax) | |
// calculate a new increment based upon the incr1 and incr2 | |
let incr = calcIncr op incr1 incr2 | |
match min, incr, max with | |
| None, None, None -> unrestricted | |
let isUnrestricted = | |
getValueRange >> ValueRange.isUnrestricted | |
/// Checks whether a `ValueRange` vr1 is a subset of | |
let createRes = | |
createSucc ("Result" |> Name.createExc) | |
let isSubSetOf vr2 vr1 = | |
match vr1, vr2 with | |
| ValueSet s1, ValueSet s2 -> s2 |> Set.isSubset s1 | |
| _ -> false | |
/// Set a `ValueRange` expr to a `ValueRange` y. | |
/// So, the result is equal to or more restrictive than the original `y`. | |
let applyExpr y expr = | |
let set get set vr = | |
match expr |> get with | |
| Some m -> vr |> set m | |
| None -> vr | |
match expr with | |
| Unrestricted -> y | |
| ValueSet vs -> | |
if vs |> Set.isEmpty then | |
Exceptions.ValueRangeEmptyValueSet | |
|> Exceptions.raiseExc | |
else | |
y |> setValues vs | |
| _ -> y |> set getMin setMin |> set getMax setMax | |
// Extend type with basic arrhythmic operations. | |
type ValueRangeCalc = | |
| Mult | |
| Div | |
| Add | |
| Subtr | |
| Expr | |
static member inline (?<-)(op, vr1, vr2) = | |
match op with | |
| Mult -> calc (*) (vr1, vr2) | |
| Div -> calc (/) (vr1, vr2) | |
| Add -> calc (+) (vr1, vr2) | |
| Subtr -> calc (-) (vr1, vr2) | |
| Expr -> applyExpr vr1 vr2 | |
module Operators = | |
let inline (^*) vr1 vr2 = (?<-) Mult vr1 vr2 | |
let inline (^/) vr1 vr2 = (?<-) Div vr1 vr2 | |
let inline (^+) vr1 vr2 = (?<-) Add vr1 vr2 | |
let inline (^-) vr1 vr2 = (?<-) Subtr vr1 vr2 | |
let inline (<==) vr1 vr2 = (?<-) Expr vr1 vr2 | |
open Informedica.Utils.Lib.BCL | |
let count v = | |
let createDto n unr vals min minincl incr max maxincl = | |
{ | |
Name = n | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
} | |
module Minimum = ValueRange.Minimum | |
let createNew n = | |
createDto n true [] None false [] None false | |
type ValueRangeException = ValueRange.Exceptions.ValueRangeException | |
module Exceptions = | |
exception VariableException of Exceptions.Message | |
let raiseExc m = m |> VariableException |> raise | |
let setMin min incl dto = | |
{ dto with | |
Unr = false | |
Min = min | |
MinIncl = incl | |
} | |
/// Create a `Variable` and passes | |
let setMax max incl dto = | |
{ dto with | |
Unr = false | |
Max = max | |
MaxIncl = incl | |
} | |
let create succ n vs = { Name = n; Values = vs } |> succ | |
/// Create a `Variable` and directly | |
/// return the result. | |
let createSucc = create id | |
let isUnrestricted = | |
| "vals" -> Vals | |
| "minincl" -> MinIncl | |
| "minexcl" -> MinExcl | |
| "incr" -> Incr | |
| "maxincl" -> MaxIncl | |
| "maxexcl" -> MaxExcl | |
| _ -> NoProp | |
createSucc ("Result" |> Name.createExc) | |
/// Apply **f** to `Variable` **var**. | |
let apply f (var: Variable) = var |> f | |
| [ v ] -> v |> Some | |
| _ -> None | |
| Vals -> dto |> setVals vs | |
| MinIncl -> dto |> setMin (vs |> getVal) true | |
| MinExcl -> dto |> setMin (vs |> getVal) false | |
| Incr -> dto |> setIncr vs | |
| MaxIncl -> dto |> setMax (vs |> getVal) true | |
| MaxExcl -> dto |> setMax (vs |> getVal) false | |
| NoProp -> dto | |
let getName v = (v |> get).Name | |
exact | |
{ | |
Name = name | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
} | |
= | |
let vals = | |
ValueRange.print | |
/// Get the `ValueRange of a `Variable`. | |
unr | |
min | |
minincl | |
max | |
maxincl | |
(Some incr) | |
vals | |
let setName n v : Variable = { v with Name = n } | |
/// Apply a `ValueRange` **vr** to | |
/// `Variable` **v**. | |
let setValueRange v vr = | |
try | |
let vr' = (v |> get).Values <== vr | |
{ v with Values = vr' } | |
let n = | |
dto.Name | |
|> Name.create succ (fun m -> m |> Name.Exceptions.raiseExc) | |
with | |
| :? ValueRangeException -> | |
(v, vr) | |
|> Exceptions.VariableCannotSetValueRange | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
|> Option.bind (fun v -> | |
v |> Minimum.createMin dto.MinIncl |> Some | |
) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
let setNonZeroOrNegative v = | |
let vr = | |
(v |> get).Values | |
| [] -> None | |
| _ -> dto.Incr |> Set.ofList |> Some | |
/// Get the number of distinct values | |
let vr = ValueRange.create min incr max vs | |
let createDto n unr vals min minincl incr max maxincl = | |
{ | |
Name = n | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
let n = | |
dto.Name |> Name.create succ (fun m -> m |> fail) | |
let createNew n = | |
createDto n true [] None false [] None false | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
|> Option.bind (fun v -> | |
v |> Minimum.createMin dto.MinIncl |> Some | |
) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
/// i.e. there is but one possible value left. | |
let isSolved v = | |
match dto.Incr with | |
&& (v |> getValueRange |> ValueRange.isValueSet) | |
| _ -> dto.Incr |> Set.ofList |> Some | |
MinIncl = incl | |
} | |
/// Checks whether a `Variable` is *solvable* | |
let setMax max incl dto = | |
{ dto with | |
Unr = false | |
with | |
| _ -> None | |
MaxIncl = incl | |
} | |
/// (or no values at all) | |
let isSolvable = isSolved >> not | |
let dto = | |
createNew (let (Name.Name n) = v.Name in n) | |
/// Checks whether there are no restrictions to | |
let unr = | |
v.Values |> ValueRange.isUnrestricted | |
let isUnrestricted = | |
| "vals" -> Vals | |
| "minincl" -> MinIncl | |
| Some m -> m |> Minimum.isMinExcl |> not | |
| None -> false | |
| "incr" -> Incr | |
| "maxincl" -> MaxIncl | |
| "maxexcl" -> MaxExcl | |
| Some m -> m |> Maximum.isMaxExcl |> not | |
| None -> false | |
(v1 |> getValueRange) |> op | |
let min = | |
|> createRes | |
/// Extend type with basic arrhythmic operations. | |
let max = | |
| [ v ] -> v |> Some | |
| _ -> None | |
| Add | |
| Subtr | |
| Vals -> dto |> setVals vs | |
| MinIncl -> dto |> setMin (vs |> getVal) true | |
| MinExcl -> dto |> setMin (vs |> getVal) false | |
| Incr -> dto |> setIncr vs | |
| Some i -> i |> Set.toList | |
| None -> [] | |
| NoProp -> dto | |
| Div -> calc (^/) (v1, v2) | |
| Add -> calc (^+) (v1, v2) | |
| Subtr -> calc (^-) (v1, v2) | |
exact | |
| Some vs -> vs |> Set.toList | |
| None -> [] | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
} | |
MaxIncl = maxincl | |
} | |
let vals = | |
ValueRange.print | |
| Expr -> | |
unr | |
min | |
minincl | |
max | |
maxincl | |
(Some incr) | |
vals | |
let inline (^*) v1 v2 = (?<-) Mult v1 v2 | |
let inline (^/) v1 v2 = (?<-) Div v1 v2 | |
let inline (^+) v1 v2 = (?<-) Add v1 v2 | |
let inline (^-) v1 v2 = (?<-) Subtr v1 v2 | |
let inline (<==) v1 v2 = (?<-) Expr v1 v2 | |
let n = | |
dto.Name | |
|> Name.create succ (fun m -> m |> Name.Exceptions.raiseExc) | |
/// Handle the creation of a `Variable` from a `Dto` and | |
/// vice versa. | |
module Dto = | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
|> Option.bind (fun v -> | |
vars |> List.filter ((=) v) |> List.length > 1 | |
) | |
) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
type Dto = | |
Unr: bool | |
Min: BigRational option | |
MinIncl: bool | |
| [] -> None | |
| _ -> dto.Incr |> Set.ofList |> Some | |
let vr = ValueRange.create min incr max vs | |
let createDto n unr vals min minincl incr max maxincl = | |
{ | |
Name = n | |
Unr = unr | |
Vals = vals | |
let createProductEqExc = | |
createProductEq id Exception.raiseExc | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
let createSumEqExc = | |
createSumEq id Exception.raiseExc | |
dto.Name |> Name.create succ (fun m -> m |> fail) | |
/// Create an *empty* *new* `Dto` with only a name **n** | |
let createNew n = | |
createDto n true [] None false [] None false | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
let isProduct = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
v |> Minimum.createMin dto.MinIncl |> Some | |
) | |
let isSum = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
/// making sure the `Unr` is set to `false`. | |
let setVals vals dto = { dto with Unr = false; Vals = vals } | |
match dto.Incr with | |
/// Set a `min` to an **dto** that is either inclusive `incl` true or exclusive `false` | |
| _ -> dto.Incr |> Set.ofList |> Some | |
MinIncl = incl | |
} | |
/// Set a `max` to an **dto** that is either inclusive `incl` true or exclusive `false` | |
let setMax max incl dto = | |
{ dto with | |
Unr = false | |
with | |
| _ -> None | |
MaxIncl = incl | |
(if c = 0 then 1 else c) * acc | |
) | |
/// Set an `incr` to a **dto** | |
let setIncr incr dto = { dto with Unr = false; Incr = incr } | |
let dto = | |
let op = | |
if eq |> isProduct then "*" else "+" | |
/// Match a string **p** to a field of `Dto` | |
let unr = | |
v.Values |> ValueRange.isUnrestricted | |
match p |> String.toLower with | |
| "vals" -> Vals | |
| "minincl" -> MinIncl | |
| Some m -> m |> Minimum.isMinExcl |> not | |
| None -> false | |
| "incr" -> Incr | |
| "maxincl" -> MaxIncl | |
| "maxexcl" -> MaxExcl | |
| Some m -> m |> Maximum.isMaxExcl |> not | |
| None -> false | |
let min = | |
/// Set a `Dto` member **p** with a value `v` to a `Dto` **dto**. | |
/// If no field can be matched the **dto** is returned unchanged. | |
let setProp p vs dto = | |
let getVal vs = | |
let max = | |
| [ v ] -> v |> Some | |
| _ -> None | |
let xs' = | |
xs |> List.map Variable.setNonZeroOrNegative | |
match p with | |
| Vals -> dto |> setVals vs | |
| MinIncl -> dto |> setMin (vs |> getVal) true | |
| MinExcl -> dto |> setMin (vs |> getVal) false | |
| Incr -> dto |> setIncr vs | |
| Some i -> i |> Set.toList | |
| None -> [] | |
| NoProp -> dto | |
/// Return a `string` representation of a `Dto` | |
let toString | |
exact | |
| Some vs -> vs |> Set.toList | |
| None -> [] | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
} | |
MaxIncl = maxincl | |
} | |
let vals = | |
ValueRange.print | |
exact | |
unr | |
vr' |> Variable.getName = (vr |> Variable.getName) | |
) | |
minincl | |
max | |
maxincl | |
(Some incr) | |
vals | |
sprintf "%s%s" name vals | |
/// Create a `Variable` from a `Dto` and | |
/// raise a `DtoException` if this fails. | |
let fromDto (dto: Dto) = | |
let succ = id | |
let vs = | |
vs |> List.replace ((Variable.eqName) v) v | |
let n = | |
dto.Name | |
|> Name.create succ (fun m -> m |> Name.Exceptions.raiseExc) | |
let vs = | |
match dto.Vals with | |
| [] -> None | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
|> Option.bind (fun v -> | |
vars |> List.filter ((=) v) |> List.length > 1 | |
) | |
) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
let incr = | |
dto.Incr | |
|> function | |
| [] -> None | |
| _ -> dto.Incr |> Set.ofList |> Some | |
let vr = ValueRange.create min incr max vs | |
create succ n vr | |
/// Create a `Variable` option from a `Dto` and | |
let createProductEqExc = | |
createProductEq id Exception.raiseExc | |
let fromDtoOpt (dto: Dto) = | |
let succ = Some | |
let fail = Option.none | |
let createSumEqExc = | |
createSumEq id Exception.raiseExc | |
dto.Name |> Name.create succ (fun m -> m |> fail) | |
let vs = | |
match dto.Vals with | |
| [] -> None | |
| _ -> dto.Vals |> Set.ofList |> Some | |
let min = | |
dto.Min | |
let isProduct = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
v |> Minimum.createMin dto.MinIncl |> Some | |
) | |
let isSum = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
let max = | |
dto.Max | |
|> Option.bind (fun v -> | |
v |> Maximum.createMax dto.MaxIncl |> Some | |
) | |
let incr = | |
match dto.Incr with | |
| [] -> None | |
| _ -> dto.Incr |> Set.ofList |> Some | |
try | |
let vr = ValueRange.create min incr max vs | |
match n with | |
| Some n' -> create succ n' vr | |
| _ -> None | |
with | |
| _ -> None | |
(if c = 0 then 1 else c) * acc | |
) | |
/// Create a `Dto` from a `Variable`. | |
let toDto (v: Variable) = | |
let dto = | |
let op = | |
if eq |> isProduct then "*" else "+" | |
let unr = | |
v.Values |> ValueRange.isUnrestricted | |
let minincl = | |
match v.Values |> ValueRange.getMin with | |
| Some m -> m |> Minimum.isMinExcl |> not | |
| None -> false | |
let maxincl = | |
match v.Values |> ValueRange.getMax with | |
| Some m -> m |> Maximum.isMaxExcl |> not | |
| None -> false | |
let min = | |
v.Values | |
|> ValueRange.getMin | |
|> Option.bind (Minimum.minToBigRational >> Some) | |
let max = | |
v.Values | |
|> ValueRange.getMax | |
let xs' = | |
xs |> List.map Variable.setNonZeroOrNegative | |
let incr = | |
v.Values | |
|> ValueRange.getIncr | |
|> function | |
let ychanged, y' = | |
calc [] op1 op1 x xs' [ y ] | |
| None -> [] | |
let vals = | |
v.Values | |
|> ValueRange.getValueSet | |
|> function | |
| Some vs -> vs |> Set.toList | |
| None -> [] | |
{ dto with | |
Unr = unr | |
Vals = vals | |
Min = min | |
MinIncl = minincl | |
Incr = incr | |
Max = max | |
MaxIncl = maxincl | |
} | |
/// Functions that handle the `Equation` type that | |
/// either represents a `ProductEquation` </br> | |
acc |> List.replaceOrAdd (Variable.eqName v) v | |
) | |
vr' |> Variable.getName = (vr |> Variable.getName) | |
) | |
/// y = x1 + x2 + ... + xn | |
module Equation = | |
open Types | |
open Variable.Operators | |
module ValueRange = Variable.ValueRange | |
module Exception = | |
/// Equation exception | |
exception EquationException of Exceptions.Message | |
let vs = | |
vs |> List.replace ((Variable.eqName) v) v | |
let raiseExc m = m |> EquationException |> raise | |
/// Create an `Equation` with an **y** and | |
/// **xs**. Fails if a variable is added more | |
/// than one time using the **fail** function. | |
/// The type of Equation product or sum | |
/// is determined by the constructor **c**. | |
let create c succ fail (y, xs) = | |
let vars = y :: xs | |
match vars | |
|> List.filter (fun v -> | |
vars |> List.filter ((=) v) |> List.length > 1 | |
) | |
with | |
| [] -> (y, xs) |> c |> succ | |
| duplicates -> | |
duplicates | |
|> Exceptions.EquationDuplicateVariables | |
|> fail | |
/// Create an `ProductEquation` with an **y** and | |
/// **xs**. Fails if a variable is added more | |
/// than one time using the **fail** function. | |
let createProductEq = create ProductEquation | |
{ | |
Vars: VariableDto [] | |
IsProdEq: bool | |
} | |
/// **xs**. Fails if a variable is added more | |
/// than one time using the **fail** function. | |
let createSumEq = create SumEquation | |
/// Create an `ProductEquation` with an **y** and | |
/// **xs**. Fails if a variable is added more | |
/// than one time raising an exception. | |
let createProductEqExc = | |
createProductEq id Exception.raiseExc | |
/// Create an `SumEquation` with an **y** and | |
/// **xs**. Fails if a variable is added more | |
/// than one time raising an exception. | |
let createSumEqExc = | |
let varToString = | |
Variable.Dto.toString exact | |
/// Apply **fp** to a `ProductEquation` and | |
/// **fs** to a `SumEquation`. | |
let apply fp fs = | |
function | |
| ProductEquation (y, xs) -> fp y xs | |
| SumEquation (y, xs) -> fs y xs | |
/// Check whether an `Equation` is a product equation | |
let isProduct = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
/// Check whether an `Equation` is a sum equation | |
let isSum = | |
apply (fun _ _ -> true) (fun _ _ -> false) | |
/// Turn an `Equation` into a list of `Variable` | |
let toVars = | |
let f y xs = y :: xs | |
apply f f | |
let count e = | |
e | |
|> toVars | |
let e = | |
(y, xs |> List.map Variable.Dto.fromDto) | |
let countProduct e = | |
match e with | |
| SumEquation _ -> -1 | |
| _ -> | |
e | |
|> toVars | |
|> List.fold | |
(fun acc v -> | |
{ | |
Vars = | |
y :: xs | |
|> List.map Variable.Dto.toDto | |
|> List.toArray | |
IsProdEq = isProd | |
} | |
let toString exact eq = | |
let op = | |
if eq |> isProduct then "*" else "+" | |
let varToString = Variable.toString exact | |
match eq |> toVars with | |
| [] -> "" | |
| _ :: [] -> "" | |
| y :: xs -> | |
let s = | |
sprintf "%s = " (y |> varToString) | |
+ (xs | |
|> List.fold | |
(fun s v -> s + (v |> varToString) + " " + op + " ") | |
"") | |
s.Substring(0, s.Length - 2) | |
/// Make sure that the `Variables` in the | |
/// `Equation` can only contain positive | |
/// non zero values. | |
let nonZeroOrNegative e = | |
let set c y xs = | |
let y' = y |> Variable.setNonZeroOrNegative | |
let xs' = | |
xs |> List.map Variable.setNonZeroOrNegative | |
|> Variable.getName | |
) | |
let fp = set ProductEquation | |
let fs = set SumEquation | |
e |> apply fp fs | |
let ychanged, y' = | |
calc [] op1 op1 x xs' [ y ] | |
/// Check whether an `Equation` contains | |
/// a `Variable` **v** | |
let contains v = | |
toVars >> (List.exists (Variable.eqName v)) | |
/// Check whether `Equation`s | |
/// **eq1** and **eq2** are equal | |
let equals eq1 eq2 = | |
let vrs1 = eq1 |> toVars | |
let vrs2 = eq2 |> toVars | |
vrs1 | |
|> List.forall (fun vr -> vrs2 |> List.exists (Variable.eqName vr)) | |
&& ((eq1 |> isProduct) && (eq2 |> isProduct) | |
|| (eq1 |> isSum) && (eq2 |> isSum)) | |
/// Find a `Variable` **vr** in | |
/// an `Equation` **eq** and return | |
/// the result in a list | |
let find vr eq = | |
eq | |
|> toVars | |
acc |> List.replaceOrAdd (Variable.eqName v) v | |
) | |
vr' |> Variable.getName = (vr |> Variable.getName) | |
) | |
/// Find a `Variable` with `Name` | |
/// **n** in an `Equation` **eq** | |
/// and return the result as a list | |
let findName n eq = | |
eq | |
|> toVars | |
|> List.filter (fun vr -> vr |> Variable.getName = n) | |
|> List.exists (fun v -> e |> Equation.contains v) | |
) | |
/// Replace a `Variable` **v** in the | |
/// `Equation` **e**. | |
let replace v e = | |
let r c v vs = | |
let vs = | |
vs |> List.replace ((Variable.eqName) v) v | |
c id (fun _ -> e) ((vs |> List.head), (vs |> List.tail)) | |
let fp y xs = r createProductEq v (y :: xs) | |
let fs y xs = r createSumEq v (y :: xs) | |
e |> apply fp fs | |
// Check whether an equation is solved | |
let isSolved = | |
function | |
| ProductEquation (y, xs) | |
| SumEquation (y, xs) -> y :: xs |> List.forall Variable.isSolved | |
// Check whether an equation will change by calc | |
// This is not the same as `isSolved`!! If all | |
// the variables are unrestricted than the equation | |
// is not solvable but is also not solved. | |
let isSolvable = | |
function | |
| ProductEquation (y, xs) | |
| SumEquation (y, xs) -> | |
let es = y :: xs | |
es |> List.exists Variable.isSolvable | |
{ | |
Vars: VariableDto [] | |
IsProdEq: bool | |
} | |
|> List.length > 1 | |
|> not | |
let check e = | |
let issub op (y: Variable) (xs: Variable list) = | |
xs | |
|> function | |
| [] -> true | |
| _ -> | |
if y.Values |> ValueRange.isValueSet | |
&& xs | |
|> List.map Variable.getValueRange | |
|> List.forall ValueRange.isValueSet then | |
y.Values | |
let varToString = | |
Variable.Dto.toString exact | |
|> ValueRange.isSubSetOf (xs |> List.reduce (op)).Values | |
else | |
true | |
if e |> isSolvable then | |
e | |
|> function | |
| ProductEquation (y, xs) -> xs |> issub (^*) y | |
| SumEquation (y, xs) -> xs |> issub (^+) y | |
let msg = | |
invalid |> Exceptions.SolverInvalidEquations | |
true | |
/// Solve an equation **e**, return a list of | |
/// changed `Variable`s. | |
let solve log eq = | |
eq | |
|> Events.EquationStartedSolving | |
|> Logging.logInfo log | |
// let runOnce y xs = | |
let e = | |
(y, xs |> List.map Variable.Dto.fromDto) | |
// y::xs | |
// |> List.filter (Variable.getValueRange >> ValueRange.isValueSet) | |
// |> List.length | |
// let c2 = (y::xs |> List.length) | |
// (c2 - c1 <= 1) | |
if eq |> isSolved then | |
[] | |
{ | |
Vars = | |
y :: xs | |
|> List.map Variable.Dto.toDto | |
|> List.toArray | |
IsProdEq = isProd | |
} | |
|> Logging.logInfo log | |
match rest with | |
| [] -> | |
(changed, xs) | |
|> Events.EquationFinishedCalculation | |
|> Logging.logInfo log | |
changed, xs | |
| x :: tail -> | |
let xs' = xs |> List.filter ((<>) x) | |
let x' = | |
match xs' with | |
| [] -> x <== y | |
| _ -> x <== (y |> op2 <| (xs' |> List.reduce op1)) | |
let changed = | |
if x = x' then | |
changed | |
else | |
x' | |
|> Events.EquationVariableChanged | |
|> Logging.logInfo log | |
changed | |
|> List.replaceOrAdd (Variable.eqName x') x' | |
tail |> calc changed op1 op2 y (x' :: xs') | |
// op1 = (*) or (+) and op2 = (/) or (-) | |
|> Variable.getName | |
) | |
let x = xs |> List.head | |
let xs' = xs |> List.filter ((<>) x) | |
// Calculate y = x1 op1 x2 op1 .. op1 xn | |
let ychanged, y' = | |
calc [] op1 op1 x xs' [ y ] | |
// Replace y with the new y with is in a list | |
let y = y' |> List.head | |
// Calculate x1 = y op2 (x2 op1 x3 .. op1 xn) | |
// and x2 = y op2 (x1 op1 x3 .. op1 xn) | |
// etc.. | |
let xchanged, xs = calc [] op1 op2 y xs xs | |
// If something has changed restart until nothing changes anymore | |
// or only has to run once | |
match ychanged @ xchanged with | |
| [] -> | |
changed | |
|> Events.EquationFinishedSolving | |
|> Logging.logInfo log | |
changed | |
| _ -> | |
ychanged @ xchanged | |
|> List.fold | |
(fun acc v -> | |
acc |> List.replaceOrAdd (Variable.eqName v) v | |
) | |
changed | |
|> fun changed -> | |
// only run once so now is ready | |
if b then | |
changed | |
else | |
(b, y, xs, changed) | |
|> Events.EquationLoopedSolving | |
|> Logging.logInfo log | |
|> List.exists (fun v -> e |> Equation.contains v) | |
) | |
loop b op1 op2 y xs changed | |
let b, y, xs, op1, op2 = | |
match eq with | |
| ProductEquation (y, xs) -> y, xs, (^*), (^/) | |
| SumEquation (y, xs) -> y, xs, (^+), (^-) | |
|> fun (y, xs, op1, op2) -> | |
// run only once when all but one is a value set | |
false, y, xs, op1, op2 // runOnce y xs, y, xs, op1, op2 | |
match xs with | |
| [] -> [] | |
| _ -> | |
try | |
loop b op1 op2 y xs [] | |
with | |
| Variable.Exceptions.VariableException m -> | |
m |> Logging.logError log | |
eq | |
|> Events.EquationCouldNotBeSolved | |
|> Logging.logWarning log | |
m |> Variable.Exceptions.raiseExc | |
module Dto = | |
type VariableDto = Variable.Dto.Dto | |
/// `Dto` for an `Equation` | |
type Dto = | |
{ | |
Vars: VariableDto [] | |
IsProdEq: bool | |
} | |
/// Create a `Dto` with `vars` (variable dto array) | |
/// that is either a `ProductEquation` or a `SumEquation` | |
let create isProd vars = { Vars = vars; IsProdEq = isProd } | |
/// Create a `ProductEquation` `Dto` | |
let createProd = create true | |
/// Create a `SumEquation` `Dto` | |
let createSum = create false | |
/// Return the `string` representation of a `Dto` | |
let toString exact (dto: Dto) = | |
let op = if dto.IsProdEq then "*" else "+" | |
let varToString = | |
Variable.Dto.toString exact | |
match dto.Vars |> Array.toList with | |
| [] -> "" | |
| _ :: [] -> "" | |
| y :: xs -> | |
let s = | |
sprintf "%s = " (y |> varToString) | |
+ (xs | |
|> List.fold | |
(fun s v -> s + (v |> varToString) + " " + op + " ") | |
"") | |
let msg = | |
invalid |> Exceptions.SolverInvalidEquations | |
/// Create a `Dto` and raise an exception if it fails | |
let fromDto dto = | |
let succ = id | |
let fail = Exception.raiseExc | |
match dto.Vars |> Array.toList with | |
| [] -> Exceptions.EquationEmptyVariableList |> fail | |
| y :: xs -> | |
let y = y |> Variable.Dto.fromDto | |
let e = | |
(y, xs |> List.map Variable.Dto.fromDto) | |
if dto.IsProdEq then | |
e |> createProductEq succ fail | |
else | |
e |> createSumEq succ fail | |
/// Create a `Dto` from an `Equation` **e** | |
let toDto e = | |
let c isProd y xs = | |
{ | |
Vars = | |
y :: xs | |
|> List.map Variable.Dto.toDto | |
|> List.toArray | |
IsProdEq = isProd | |
} | |
let fp = c true | |
let fs = c false | |
e |> apply fp fs | |
/// Implementations of solvers for product equations | |
/// sum equations and a set of product and/or sum | |
/// equations | |
module Solver = | |
module EQD = Equation.Dto | |
open Types | |
module Exception = | |
/// Equation exception | |
exception SolverException of Exceptions.Message | |
/// Raise an `EquationException` with `Message` `m`. | |
let raiseExc m = m |> SolverException |> raise | |
let sortByName eqs = | |
eqs | |
|> List.sortBy (fun e -> | |
e | |
|> Equation.toVars | |
|> List.head | |
|> Variable.getName | |
) | |
/// Format a set of equations to print. | |
/// Using **f** to allow additional processing | |
/// of the string. | |
let printEqs exact pf eqs = | |
"equations result:\n" |> pf | |
eqs | |
|> sortByName | |
|> List.map (Equation.toString exact) | |
|> List.iteri (fun i s -> s |> sprintf "%i.\t%s" i |> pf) | |
"-----" |> pf | |
eqs | |
/// Checks whether a list of `Equation` **eqs** | |
/// contains an `Equation` **eq** | |
let contains eq eqs = eqs |> List.exists ((=) eq) | |
/// The `Result` of solving an `Equation` | |
/// is that either the `Equation` is the | |
/// same or has `Changed`. | |
type Result = | |
| UnChanged | |
| Changed of Variable list | |
/// Replace a list of `Variable` **vs** | |
/// in a list of `Equation` **es**, return | |
/// a list of replaced `Equation` and a list | |
/// of unchanged `Equation` | |
let replace vars es = | |
let rpl, rst = | |
es | |
|> List.partition (fun e -> | |
vars | |
|> List.exists (fun v -> e |> Equation.contains v) | |
) | |
vars | |
|> List.fold (fun acc v -> acc |> List.map (Equation.replace v)) rpl, | |
rst | |
/// Solve the equation `e` and return | |
/// the set of equations `es` it belongs | |
/// to either as `Changed` or `Unchanged` | |
let solveEquation log e = | |
let changed = e |> Equation.solve log | |
if changed |> List.length > 0 then | |
changed |> Changed | |
else | |
UnChanged | |
let memSolve f = | |
let cache = ref Map.empty | |
fun e -> | |
match (!cache).TryFind(e) with | |
| Some r -> r | |
| None -> | |
let r = f e | |
cache := (!cache).Add(e, r) | |
r | |
let sortQue que = | |
if que |> List.length = 0 then | |
que | |
else | |
que |> List.sortBy Equation.countProduct | |
/// Create the equation solver using a | |
/// product equation and a sum equation solver | |
/// and function to determine whether an | |
/// equation is solved | |
let solve log sortQue vr eqs = | |
let solveE = solveEquation log | |
let rec loop n que acc = | |
if n > ((que @ acc |> List.length) * 10) then | |
(que @ acc) | |
|> Exceptions.SolverLooped | |
|> Logging.logError log | |
(que @ acc) | |
|> Exceptions.SolverLooped | |
|> Exception.raiseExc | |
let que = que |> sortQue | |
que | |
|> Events.SolverLoopedQue | |
|> Logging.logInfo log | |
match que with | |
| [] -> | |
match acc |> List.filter (Equation.check >> not) with | |
| [] -> acc | |
| invalid -> | |
let msg = | |
invalid |> Exceptions.SolverInvalidEquations | |
msg |> Logging.logError log | |
msg |> Exception.raiseExc | |
| eq :: tail -> | |
// If the equation is already solved, or not solvable | |
// just put it to the accumulated equations and go on with the rest | |
if eq |> Equation.isSolvable |> not then | |
[ eq ] |> List.append acc |> loop (n + 1) tail | |
// Else go solve the equation | |
else | |
match eq |> solveE with | |
// Equation is changed, so every other equation can | |
// be changed as well (if changed vars are in the other | |
// equations) so start new | |
| Changed vars -> | |
let eq = [ eq ] |> replace vars |> fst | |
// find all eqs with vars in acc and put these back on que | |
acc | |
|> replace vars | |
|> function | |
| (rpl, rst) -> | |
// replace vars in tail | |
let que = | |
tail | |
|> replace vars | |
|> function | |
| (es1, es2) -> | |
es1 | |
|> List.append es2 | |
|> List.append rpl | |
rst |> List.append eq |> loop (n + 1) que | |
// Equation did not in fact change, so put it to | |
// the accumulated equations and go on with the rest | |
| UnChanged -> | |
[ eq ] |> List.append acc |> loop (n + 1) tail | |
eqs | |
|> replace [ vr ] | |
|> function | |
| (rpl, rst) -> loop 0 rpl rst |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment