Skip to content
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
99 changes: 87 additions & 12 deletions docs/src/howto/howto_3.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The rest of this document will be organized as follows:
2. The `run` function
3. The `analyze` function
4. Plotting and the Explorer UI
5. Simulation Modification Functions
5. Other Useful Functions
6. Examples

*We will refer separately to two types, `SimulationDef` and `SimulationInstance`. They are referred to as `sim_def` and `sim_inst` respectively as function arguments, and `sd` and `si` respectively as local variables.*
Expand Down Expand Up @@ -62,9 +62,30 @@ If using Latin Hypercube Sampling (LHS) is used, the following function must als

- `quantile(dist, quantiles::Vector{Float64})` which returns values for the given `quantiles` of the distribution.

In addition to the distributions available in the `Distributions` package, Mimi provides:
In addition to the distributions available in the `Distributions` package, Mimi provides the following options. Note that these are not exported by default, so they need to either be explicitly imported (ie. `import Mimi: EmpiricalDistribution`) or prefixed with `Mimi.` when implemented (ie. `Mimi.EmpiricalDistribution(vals, probs)`):

- `EmpiricalDistribution`, which takes a vector of values and (optional) vector of probabilities and produces samples from these values using the given probabilities, if provided, or equal probability otherwise.
- `EmpiricalDistribution`, which takes a vector of values and (optional) vector of probabilities and produces samples from these values using the given probabilities, if provided, or equal probability otherwise. To use this in a `@defsim`, you might do:

```julia
using CSVFiles
using DataFrames
using Mimi
import Mimi: EmpiricalDistribution # not currently exported so you just need to grab it

# read in your values
values = load("path_to_values_file"; header_exists = false) |> DataFrame
# read in your probabilities (optional, if none are provided we assume all equal)
# note that the probabilities need to be Float type and should roughly add to 1
probs = load("path_to_probabilities_file"; header_exists = false) |> DataFrame

# create your simulation
@defsim begin
...
trc_transientresponse = EmpiricalDistribution(values, probs)
...
end
```
Note there are many ways to load values, we use DataFrames and CSVFiles above but there might be an easier way depending on what packages you like

- `SampleStore{T}`, which stores a vector of samples that are produced in order by the `rand` function. This allows the user to to store a predefined set of values (useful for regression testing) and it is used by the LHS method, which draws all required samples at once at equal probability intervals and then shuffles the values. It is also used when rank correlations are specified, since this requires re-ordering draws from random variables.

Expand All @@ -85,7 +106,7 @@ The macro next defines how to apply the values generated by each RV to model par
- `param += RV` replaces the values in the parameter with the sum of the original value and the value of the RV for the current trial.
- `param *= RV` replaces the values in the parameter with the product of the original value and the value of the RV for the current trial.

As described below, in `@defsim`, you can apply distributions to specific slices of array parameters, and you can "bulk assign" distributions to elements of a vector or matrix using a more condensed syntax.
As described below, in `@defsim`, you can apply distributions to specific slices of array parameters, and you can "bulk assign" distributions to elements of a vector or matrix using a more condensed syntax. Note that these relationship assignments are referred to as **transforms**, and are referred to later in this documentation in the `add_transform!` and `delete_transform!` helper functions.

#### Apply RVs to model parameters: Assigning to array slices

Expand Down Expand Up @@ -338,16 +359,70 @@ plot(sim_inst::SimulationInstance, comp_name::Symbol, datum_name::Symbol; intera

![Plot Simulation Example](../figs/plot_sim_example.png)

## 5. Simulation Modification Functions
## 5. Other Useful Functions

### Simulation Modification Functions

A small set of unexported functions are available to modify an existing `SimulationDefinition`. The functions include:
* `delete_RV!`
* `add_RV!`
* `replace_RV!`
* `delete_transform!`
* `add_transform!`
* `delete_save!`
* `add_save!`

* `delete_RV!(sim_def::SimulationDef, name::Symbol)` - deletes the random variable with name `name` from the Simulation Definition `sim_def`, along with all transformations using that random variable
* `add_RV!(sim_def::SimulationDef, name::Symbol, dist::Distribution)` - add the random variable with the name `name` from the Simulation Definition `sim_def`
* `replace_RV!(sim_def::SimulationDef, name::Symbol, dist::Distribution)` - replace the random variable with name `name` in Simulation Definition with a random variable of the same `name` but with the distribution `Distribution`

* `delete_transform!(sim_def::SimulationDef, name::Symbol)!` - Delete all data transformations in Simulation Definition `sim_def` (i.e., replacement, addition or multiplication) of original data values with values drawn from the random variable named `name`
* `add_transform!(sim_def::SimulationDef, paramname::Symbol, op::Symbol, rvname::Symbol, dims::Vector=[])!` - Create a new `TransformSpec` based on `paramname`, `op`, `rvname` and `dims` to the Simulation Definition `sim_def`. The symbol `rvname` must refer to an existing random variable, and `paramname` must refer to an existing parameter. If `dims` are provided, these must be legal subscripts of `paramname`. Op must be one of :+=, :*=, or :(=).

For example, say a user starts off with a SimulationDefinition `MySimDef` with a parameter `MyParameter` drawn from the random variable `MyRV` with distribution `Uniform(0,1)`.

Case 1: The user wants this random variable to draw from a new distribution, say `Normal(0,1)`, which will affect all parameters with transforms attached to this random variable.
```
using Distributions
using Mimi
replace_RV!(MySimDef, MyRV, Normal(0,1))
```
Case 2: The user parameter `MyParameter` to to take on the value of a random draw from a `Normal(2,0.1)` distribution. We assume this requires a new random variable, because no random variable has this distribution yet.
```
using Distributions
using Mimi
add_RV!(MySimDef, :NewRV, Normal(2, 0.1))
add_transform!(MySimDef, :MyParameter, :=, :NewRV)
```

* `add_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol)` - Add to Simulation Definition`sim_def` a "save" instruction for component `comp_name` and parameter or variable `datum_name`. This result will be saved to a CSV file at the end of the simulation.
* `delete_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol)` - Delete from Simulation Definition `sim_def` a "save" instruction for component `comp_name` and parameter nor variable `datum_name`. This result will no longer be saved to a CSV file at the end of the simulation.

### Helper Functions

```
"""
get_simdef_rvnames(sim_def::SimulationDef, name::Union{String, Symbol})

A helper function to support finding the keys in a Simulation Definition `sim_def`'s
rvdict that contain a given `name`. This can be particularly helpful if the random
variable was set via the shortcut syntax ie. my_parameter = Normal(0,1) and thus the
`sim_def` has automatically created a key in the format `:my_parameter!x`. In this
case this function is useful to get the key and carry out modification functions
such as `replace_rv!`.
"""

function get_simdef_rvnames(sim_def::SimulationDef, name::Union{String, Symbol})
names = String.([keys(sim_def.rvdict)...])
matches = Symbol.(filter(x -> occursin(String(name), x), names))
end
```
As shown in the examples below, and described above, parameters can be assigned unique random variables under the hood without explicitly declaring the RV. Fore example, instead of pairing
```
rv(name) = Uniform(0.2, 0.8)
share = name1
```
we can write
```
share = Uniform(0.2, 0.8)
```
When this is done, Mimi will create a new unique RV with a unique name `share!x` where `x` is an integer determined by internal processes that gaurantee it to be unique in this `sim_def`. This syntax is therefore not recommended if the user expects to want to reference that random variable using the aforementioned modification functions. That said, if the user does need to do so we have added a helper function `get_simdef_rvnames(sim_def::SimulationDef, name::Union{String, Symbol})` which will return the unique names of the random variables that start with `name`. In the case above, for example, `get_simdef_rvnames(sim_def, :share)` would return `[share!x]`. In a case where share had multiple dimensions, like three regions, it would return `[share!x1, share!x2, share!x3]`.

### Payload Functions

* `set_payload!`
* `payload`

Expand Down
9 changes: 6 additions & 3 deletions docs/src/tutorials/tutorial_5.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,21 +366,24 @@ scc_results = Mimi.payload(si)[2] # Recall that the SCC array was the second o

```

#### Simulation Modification Functions
A small set of unexported functions are available to modify an existing `SimulationDef`. The functions include:
#### Other Helpful Functions

A small set of unexported functions are available to modify an existing `SimulationDef`. Please refer to How-to Guide 3: Conduct Monte Carlo Simulations and Sensitivity Analysis for an in depth description of their use cases. The functions include the following:

* `delete_RV!`
* `add_RV!`
* `replace_RV!`
* `delete_transform!`
* `add_transform!`
* `delete_save!`
* `add_save!`
* `get_simdef_rvnames`
* `set_payload!`
* `payload`

#### Full list of keyword options for running a simulation

View How-to Guide 3: Conduct Sensitivity Analysis for **critical and useful details on the full signature of this function**, as well as more details and optionality for more advanced use cases.
View How-to Guide 3: Conduct Monte Carlo Simulations and Sensitivity Analysis for **critical and useful details on the full signature of this function**, as well as more details and optionality for more advanced use cases.

```julia
function Base.run(sim_def::SimulationDef{T}, models::Union{Vector{Model}, Model}, samplesize::Int;
Expand Down
66 changes: 45 additions & 21 deletions src/mcs/defmcs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,24 @@ end
"""
delete_RV!(sim_def::SimulationDef, name::Symbol)

Delete the random variable `name` from the Simulation definition `sim`.
Delete the random variable with name `name` from the Simulation definition `sim_def`.
Transformations using this RV are deleted, and the Simulation's
NamedTuple type is updated to reflect the dropped RV.
"""
function delete_RV!(sim_def::SimulationDef, name::Symbol)
delete_transform!(sim_def, name)
delete!(sim_def.rvdict, name)
_update_nt_type!(sim_def)
if !haskey(sim_def.rvdict, name)
@warn("Simulation def does not have RV :$name. Nothing being deleted.")
else
delete_transform!(sim_def, name)
delete!(sim_def.rvdict, name)
_update_nt_type!(sim_def)
end
end

"""
add_RV!(sim_def::SimulationDef, rv::RandomVariable)

Add random variable definition `rv` to Simulation definition `sim`. The
Add random variable definition `rv` to Simulation definition `sim_def`. The
Simulation's NamedTuple type is updated to include the RV.
"""
function add_RV!(sim_def::SimulationDef, rv::RandomVariable)
Expand All @@ -209,26 +213,28 @@ end
"""
add_RV!(sim_def::SimulationDef, name::Symbol, dist::Distribution)

Add random variable definition `rv` to Simulation definition `sim`. The
Simulation's NamedTuple type is updated to include the RV.
Add a random variable with name `name` and distribution `dist` to Simulation definition
`sim_def`. The Simulation's NamedTuple type is updated to include the RV.
"""
add_RV!(sim_def::SimulationDef, name::Symbol, dist::Distribution) = add_RV!(sim_def, RandomVariable(name, dist))

"""
replace_RV!(sim_def::SimulationDef, rv::RandomVariable)

Replace the RV with the given `rv`s name in the Simulation definition Sim with
Replace the RV with the given `rv`s name in the Simulation definition `sim_def` with
`rv` and update the Simulation's NamedTuple type accordingly.
"""
function replace_RV!(sim_def::SimulationDef, rv::RandomVariable)
name = rv.name
haskey(sim_def.rvdict, name) || error("Simulation def does not have has RV :$name. Use add_RV! to add it.")
sim_def.rvdict[rv.name] = rv
_update_nt_type!(sim_def)
end

"""
replace_RV!(sim_def::SimulationDef, name::Symbol, dist::Distribution)

Replace the with name `name` in the Simulation definition Sim with a new RV
Replace the rv with name `name` in the Simulation definition `sim_def` with a new RV
with `name` and distribution `dist`. Update the Simulation's NamedTuple
type accordingly.
"""
Expand All @@ -243,14 +249,18 @@ Simulation definition's NamedTuple type accordingly.
"""
function delete_transform!(sim_def::SimulationDef, name::Symbol)
pos = findall(t -> t.rvname == name, sim_def.translist)
deleteat!(sim_def.translist, pos)
_update_nt_type!(sim_def)
if isempty(pos)
@warn("Simulation def does not have and transformations using RV :$name. Nothing being deleted.")
else
deleteat!(sim_def.translist, pos)
_update_nt_type!(sim_def)
end
end

"""
add_transform!(sim_def::SimulationDef, t::TransformSpec)

Add the data transformation `t` to the Simulation definition `sim`, and update the
Add the data transformation `t` to the Simulation definition `sim_def`, and update the
Simulation's NamedTuple type. The TransformSpec `t` must refer to an
existing RV.
"""
Expand All @@ -263,7 +273,7 @@ end
add_transform!(sim_def::SimulationDef, paramname::Symbol, op::Symbol, rvname::Symbol, dims::Vector{T}=[]) where T

Create a new TransformSpec based on `paramname`, `op`, `rvname` and `dims` to the
Simulation definitino `sim`, and update the Simulation's NamedTuple type. The symbol `rvname` must
Simulation definition `sim_def`, and update the Simulation's NamedTuple type. The symbol `rvname` must
refer to an existing RV, and `paramname` must refer to an existing parameter. If `dims` are
provided, these must be legal subscripts of `paramname`. Op must be one of :+=, :*=, or :(=).
"""
Expand All @@ -274,19 +284,19 @@ end
"""
delete_save!(sim_def::SimulationDef, key::Tuple{Symbol, Symbol})

Delete from Simulation definition `sim` a "save" instruction for the given `key`, comprising
Delete from Simulation definition `sim_def` a "save" instruction for the given `key`, comprising
component `comp_name` and parameter or variable `datum_name`. This result will no
longer be saved to a CSV file at the end of the simulation.
"""
function delete_save!(sim_def::SimulationDef, key::Tuple{Symbol, Symbol})
pos = findall(isequal(key), sim_def.savelist)
deleteat!(sim_def.savelist, pos)
isempty(pos) ? @warn("Simulation def doesn't have $key in its save list. Nothing being deleted.") : deleteat!(sim_def.savelist, pos)
end

"""
delete_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol)

Delete from Simulation definition `sim` a "save" instruction for component `comp_name` and parameter
Delete from Simulation definition `sim_def` a "save" instruction for component `comp_name` and parameter
or variable `datum_name`. This result will no longer be saved to a CSV file at the end
of the simulation.
"""
Expand All @@ -295,20 +305,34 @@ delete_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol) = de
"""
add_save!(sim_def::SimulationDef, key::Tuple{Symbol, Symbol})

Add to Simulation definition `sim` a "save" instruction for the given `key`, comprising
Add to Simulation definition `sim_def` a "save" instruction for the given `key`, comprising
component `comp_name` and parameter or variable `datum_name`. This result will
be saved to a CSV file at the end of the simulation.
"""
function add_save!(sim_def::SimulationDef, key::Tuple{Symbol, Symbol})
delete_save!(sim_def, key)
push!(sim_def.savelist, key)
nothing
pos = findall(isequal(key), sim_def.savelist)
!isempty(pos) ? @warn("Simulation def already has $key in its save list. Nothing being added.") : push!(sim_def.savelist, key)
end

"""
add_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol)

Add to Simulation definition`sim` a "save" instruction for component `comp_name` and parameter or
Add to Simulation definition`sim_def` a "save" instruction for component `comp_name` and parameter or
variable `datum_name`. This result will be saved to a CSV file at the end of the simulation.
"""
add_save!(sim_def::SimulationDef, comp_name::Symbol, datum_name::Symbol) = add_save!(sim_def, (comp_name, datum_name))

"""
get_simdef_rvnames(sim_def::SimulationDef, name::Union{String, Symbol})

A helper function to support finding the keys in a Simulation Definition `sim_def`'s
rvdict that contain a given `name`. This can be particularly helpful if the random
variable was set via the shortcut syntax ie. my_parameter = Normal(0,1) and thus the
`sim_def` has automatically created a key in the format `:my_parameter!xx`. In this
case this function is useful to get the key and carry out modification functions
such as `replace_rv!`.
"""
function get_simdef_rvnames(sim_def::SimulationDef, name::Union{String, Symbol})
names = String.([keys(sim_def.rvdict)...])
matches = Symbol.(filter(x -> occursin(String(name), x), names))
end
3 changes: 3 additions & 0 deletions test/mcs/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ using Test
@info("test_defmcs.jl")
include("test_defmcs.jl")

@info("test_defmcs_modifications.jl")
include("test_defmcs_modifications.jl")

@info("test_defmcs_sobol.jl")
include("test_defmcs_sobol.jl")

Expand Down
Loading