Context
- Model the parameters to a MP optimisation problem: Indices and parameters
- The same parameter object should be usable for different optimisation problems, ignoring extra data.
- Everything should be typesafe.
Flexible Records
- Flexible Records seem a good first approach.
- But they have to be declared everywhere and do not compose automatically.
- That is, no implicit row variables / record union.
Interfaces
- Interfaces
- Compose and even allow diamond inheritance.
- If basic functions use simple interfaces, then the constraints are propagated up the call chain.
- See Dealing with complex dependency injection in F#
- Layer the interfaces
- First layer: index sets
- Each independent index set gets its own interface
- Composite/dependent index sets
- Second layer: parameters
- Indexed by first layer indices (if necessary)
- Third layer
- Custom interfaces for models, for leaner type annotation
- First layer: index sets
- Design decision
- Each interface has one member named the same way as the interface
Example
# Layer 1
type IndexA =
abstract member IndexA : array<int>
type IndexB<'a when 'a : comparison> =
abstract member IndexB : Set<'a>
type IndexC<'a when 'a : comparison> =
inherit IndexSetA<string> # base set of elements in IndexC
abstract member IndexC : Set<'a * 'a>
# Layer 2
type ParamA =
abstract member ParamA : int
type ParamB =
inherit ParamA # length of ParamB
abstract member ParamB : array<int>
type ParamC =
inherit ParamA # length of ParamC
abstract member ParamC : array<int>
type ParamD<'a when 'a : comparison> =
inherit ParamA # length of arrays in ParamD
inherit IndexA<'a> # keys of ParamD
abstract member ParamD : Map<'a, array<float>
type ParamE<'a when 'a : comparison> =
inherit IndexC<'a> # keys of ParamE
abstrac member ParamE: Map<'a, Map<'a, bool>>
# Layer 3
type Version1 =
ParamB
ParamD<string>
ParamE<string>
type Version2 =
ParamB
ParamC
ParamD<string>
ParamE<string>