From 551ec170b21803fa69c5f49d6d9f1ee5b6f24443 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 31 Aug 2018 17:46:15 -0700 Subject: [PATCH 01/81] Added start of metamodel code --- src/core/metamodel.jl | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/core/metamodel.jl diff --git a/src/core/metamodel.jl b/src/core/metamodel.jl new file mode 100644 index 000000000..3fdcd70ea --- /dev/null +++ b/src/core/metamodel.jl @@ -0,0 +1,79 @@ +abstract struct MetaComponent end + +struct Component <: MetaComponent + pars::Vector{Parameter} + vars::Vector{Variable} + dims::Vector{Symbol} + cd::ComponentDef + ci::ComponentInstance +end + +struct Model <: MetaComponent + pars::Vector{ParameterInstance} # these refer to pars/vars inside a model's comps + vars::Vector{VariableInstance} + dims::Vector{Symbol} + comps::Vector{MetaComponent} + mi::union{ModelInstance, Void} + md::ModelDef + + function new(md::ModelDef) + m = Model(md) + m.mi = nothing + m.comps = Vector{MetaComponent}() # compute these when the model is built + m.pars = Vector{ParameterInstance}() + m.vars = Vector{VariableInstance}() + m.vars = Vector{VariableInstance}() + return m + end +end + +# +# These funcs provide the API to MetaComponents +# + +variables(c::Component) = variables(c.ci) + +variables(m::Model) = variables(m.params) + + +parameters(c::Component) = parameters(c.ci) + +parameters(m::Model) = parameters(m.vars) + + +components(m::Model) = components(m.mi) # should already be a @modelegate for this + +components(c::Component) = [] + + +build(c::Component) = nothing + +function build(m::Model) + for c in components(m) + (vars, pars, dims) = build(c) + + # Add vars, pars, dims to our list + end + + m.mi = build(m.md) + + return # vars, pars, dims +end + + +reset(c::Component) = reset(c.ci) + +function reset(m::Model) + for c in components(m) + reset(c) + end +end + + +run_timestep(c::Component, args...) = c.ci.run_timestep(args...) + +function run_timestep(m::Model) + for c in components(m) + run_timestep(c) + end +end From ab2ffd20603e42a17ca430c4d840c1449cbdff23 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 11 Sep 2018 13:23:58 -0700 Subject: [PATCH 02/81] WIP -- thoughs on meta-components --- docs/src/internals/composite_components.md | 46 +++++++ src/core/metamodel.jl | 142 ++++++++++++++++----- 2 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 docs/src/internals/composite_components.md diff --git a/docs/src/internals/composite_components.md b/docs/src/internals/composite_components.md new file mode 100644 index 000000000..b9f6a67e9 --- /dev/null +++ b/docs/src/internals/composite_components.md @@ -0,0 +1,46 @@ +# Composite Components + +## Goals + +In Mimi v0.4, we have two levels of model elements, (i) `Model` and (ii) `Component`. (For now, we ignore the distinction between model / component "definition" and "instance" types.) The primary goal of the `MetaComponent` construct is to extend this structure recursively to _N_ levels, by allowing a `MetaComponent` to contain other `MetaComponent`s as well as `LeafComponent`s, which cannot contain other components. + +## Major elements +This suggests three types of elements: + +1. `LeafComponent` -- equivalent to the Mimi v0.4 `Component` concept. + +1. `MetaComponent` -- presents the same API as a `LeafComponent`, but the variables and parameters it exposes are the aggregated sets of variables and parameters exposed by its components, each of which can be a `MetaComponent` or `LeafComponent`. A `MetaComponent` creates no new storage for variables and parameters; it references the storage in its internal components. The `run_timestep` method of a `MetaComponent` simply calls the `run_timestep` method of each of its internal components in order. + +1. `Model` -- Like a `MetaComponent`, a model contains one or more instances of `MetaComponent` or `LeafComponent`. However, the API for a `Model` differs from that of a `MetaComponent`, thus they are separate classes. For example, you can run a Monte Carlo simulation on a `Model`, but not on a component (of either type). + + +## Implementation Notes + +### Model + +* A `Model` will be defined using the `@defmodel` macro. + +* As with the currently defined (but not exported) `@defmodel`, component ordering will be determined automatically based on defined connections, with loops avoided by referencing timestep `[t-1]`. This simplifies the API for `addcomponent!`. + +* We will add support for two optional functions defined inside `@defmodel`: `before_run` and `after_run`, which are called before and after (respectively) the model is run over all its timesteps. + +* A `Model` will be implemented as a wrapper around a single top-level `MetaComponent` that handles the ordering and iteration over sub-components. (In an OOP language, `Model` would subclass `MetaComponent`.) + + +### MetaComponent + +* Defined using `@defcomp` as with `LeafComponent`. It's "meta" nature is defined by including a new term: + + `subcomps = [sc1, sc2, sc3, ...]`, where the referenced sub-components (`sc1`, etc.) refer to previously defined `ComponentId`s. + +* A `MetaComponent`'s `run_timestep` function is optional. The default function simply calls `run_timestep(subcomps::Vector)` to iterate over sub-components and calls `run_timestep` on each. If a `MetaComponent` defines its own `run_timestep` function, it should either call `run_timestep` on the vector of sub-components or perform a variant of this function itself. + +### Other stuff + +* This is a good opportunity to reconsider the treatment of external parameters. The main question is about naming these and whether they need to be globally unique or merely unique within a (meta) component. + +* It turns out that generic functions and dynamic dispatch are not optimal for all design cases. Specifically: + * The case of iterating over a vector of heterogenous objects and calling a function on each is handled poorly with dynamic dispatch. In Mimi, we generate unique functions for these and store them in a pointer, OOP style, so we can call them directly without the cost of dynamic dispatch. This, too, could be handled in a more automated fashion via an OOP macro. + +* The lack of inheritance requires code duplication in the cases where multiple types share the same structure or a portion thereof. An OOP macro could handle this by generating the duplicate structure in two types that share the same abstract type, allowing a single point of modification for the shared elements. + * This could be handled with composition, i.e., defined shared type and have an instance of it in each of the types that share this structure. The extra layer should disappear after compilation. diff --git a/src/core/metamodel.jl b/src/core/metamodel.jl index 3fdcd70ea..7aca80187 100644 --- a/src/core/metamodel.jl +++ b/src/core/metamodel.jl @@ -1,25 +1,39 @@ -abstract struct MetaComponent end - -struct Component <: MetaComponent - pars::Vector{Parameter} - vars::Vector{Variable} - dims::Vector{Symbol} - cd::ComponentDef - ci::ComponentInstance +# Create variants for *Instance and *Def + +abstract struct AbstractComponentDef <: NamedDef end + +mutable struct LeafComponentDef <: AbstractComponentDef + name::Symbol + comp_id::ComponentId + variables::OrderedDict{Symbol, DatumDef} + parameters::OrderedDict{Symbol, DatumDef} + dimensions::OrderedDict{Symbol, DimensionDef} + first::Int + last::Int end -struct Model <: MetaComponent - pars::Vector{ParameterInstance} # these refer to pars/vars inside a model's comps - vars::Vector{VariableInstance} - dims::Vector{Symbol} - comps::Vector{MetaComponent} +# *Def implementation doesn't need to be performance-optimized since these +# are used only to create *Instance objects that are used at run-time. With +# this in mind, we don't create dictionaries of vars, params, or dims in the +# MetaComponentDef since this would complicate matters if a user decides to +# add/modify/remove a component. Instead of maintaining a secondary dict, we +# just iterate over sub-components at run-time as needed. + +struct MetaComponentDef <: AbstractComponentDef + name::Symbol + comp_id::ComponentId + comps::Vector{AbstractComponent} +end + +struct Model + metacomp::MetaComponent mi::union{ModelInstance, Void} md::ModelDef function new(md::ModelDef) m = Model(md) m.mi = nothing - m.comps = Vector{MetaComponent}() # compute these when the model is built + m.comps = Vector{AbstractComponent}() # compute these when the model is built m.pars = Vector{ParameterInstance}() m.vars = Vector{VariableInstance}() m.vars = Vector{VariableInstance}() @@ -27,23 +41,80 @@ struct Model <: MetaComponent end end -# -# These funcs provide the API to MetaComponents -# -variables(c::Component) = variables(c.ci) +abstract struct AbstractComponentInstance end + +mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + comp_name::Symbol + comp_id::ComponentId + variables::TV + parameters::TP + dim_dict::Dict{Symbol, Vector{Int}} + + first::Int + last::Int + + init::Union{Void, Function} # use same implementation here? + run_timestep::Union{Void, Function} +end -variables(m::Model) = variables(m.params) +struct MetaComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + # TV, TP, and dim_dict are computed by aggregating all the vars and params from the MetaComponent's + # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the + # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. + leaf::LeafComponentInstance{TV, TP} + comps::Vector{AbstractComponentInstance} +end -parameters(c::Component) = parameters(c.ci) +components(obj::MetaComponentInstance) = obj.comps -parameters(m::Model) = parameters(m.vars) +mutable struct ModelInstance + md::ModelDef + comp::Union{Void, MetaComponentInstance} -components(m::Model) = components(m.mi) # should already be a @modelegate for this + # Push these down to comp? + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} -components(c::Component) = [] + function ModelInstance(md::ModelDef) + self = new() + self.md = md + self.comp = nothing + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + return self + end +end + +# If using composition with LeafComponentInstance, we just delegate from +# MetaComponentInstance to the internal (summary) LeafComponentInstance. +compid(c::LeafComponentInstance) = c.comp_id +name(c::LeafComponentInstance) = c.comp_name +dims(c::LeafComponentInstance) = c.dim_dict +variables(c::LeafComponentInstance) = c.variables +parameters(c::LeafComponentInstance) = c.parameters + +macro delegate(ex) + if @capture(ex, fname_(varname_::MetaComponentInstance, args__) => rhs_) + result = esc(:($fname($varname::Model, $(args...)) = $fname($varname.$rhs, $(args...)))) + println(result) + return result + end + + error("Calls to @delegate must be of the form 'func(m::Model, args...) => X', where X is a field of X to delegate to'. Expression was: $ex") +end + +@delegate compid(c::MetaComponentInstance) => leaf +@delegate name(c::MetaComponentInstance) => leaf +@delegate dims(c::MetaComponentInstance) => leaf +@delegate variables(c::LeafComponentInstance) => leaf +@delegate parameters(c::LeafComponentInstance) => leaf + +@delegate variables(mi::ModelInstance) => comp +@delegate parameters(mi::ModelInstance) => comp +@delegate components(mi::ModelInstance) => comp build(c::Component) = nothing @@ -63,17 +134,26 @@ end reset(c::Component) = reset(c.ci) -function reset(m::Model) - for c in components(m) +function reset(mci::MetaComponentInstance) + for c in components(mci) reset(c) end end +function run_timestep(ci::AbstractComponentInstance, t::AbstractTimestep) + if ci.run_timestep != nothing + ci.run_timestep(parameters(ci), variables(ci), dims(ci), t) + end + + nothing +end -run_timestep(c::Component, args...) = c.ci.run_timestep(args...) +# This function is called by the default run_timestep defined by @defcomp when +# the user defines sub-components and doesn't define an explicit run_timestep. +function _meta_run_timestep(p, v, d, t) + for ci in components(mci) + ci.run_timestep(ci, t) + end -function run_timestep(m::Model) - for c in components(m) - run_timestep(c) - end -end + nothing +end \ No newline at end of file From a1ed5b2c0f5907aa3298d582cf1b80a6cef6ecf3 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 11 Sep 2018 13:47:07 -0700 Subject: [PATCH 03/81] Improved @delegate macro --- src/core/metamodel.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/metamodel.jl b/src/core/metamodel.jl index 7aca80187..4d904d776 100644 --- a/src/core/metamodel.jl +++ b/src/core/metamodel.jl @@ -96,14 +96,16 @@ dims(c::LeafComponentInstance) = c.dim_dict variables(c::LeafComponentInstance) = c.variables parameters(c::LeafComponentInstance) = c.parameters +# Convert a list of args with optional type specs to just the arg symbols +_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] + macro delegate(ex) - if @capture(ex, fname_(varname_::MetaComponentInstance, args__) => rhs_) - result = esc(:($fname($varname::Model, $(args...)) = $fname($varname.$rhs, $(args...)))) - println(result) + if @capture(ex, fname_(varname_::T_, args__) => rhs_) + argnames = _arg_names(args) + result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) return result end - - error("Calls to @delegate must be of the form 'func(m::Model, args...) => X', where X is a field of X to delegate to'. Expression was: $ex") + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") end @delegate compid(c::MetaComponentInstance) => leaf From aa43cf020bc8e1938cb5d242369c33a98310bd62 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 12 Sep 2018 10:43:42 -0700 Subject: [PATCH 04/81] WIP - Continued design work --- docs/src/internals/composite_components.md | 14 +++ src/core/metamodel.jl | 130 ++++++++++++--------- 2 files changed, 87 insertions(+), 57 deletions(-) diff --git a/docs/src/internals/composite_components.md b/docs/src/internals/composite_components.md index b9f6a67e9..f6a60eb07 100644 --- a/docs/src/internals/composite_components.md +++ b/docs/src/internals/composite_components.md @@ -35,6 +35,20 @@ This suggests three types of elements: * A `MetaComponent`'s `run_timestep` function is optional. The default function simply calls `run_timestep(subcomps::Vector)` to iterate over sub-components and calls `run_timestep` on each. If a `MetaComponent` defines its own `run_timestep` function, it should either call `run_timestep` on the vector of sub-components or perform a variant of this function itself. +## Questions + +* Currently, `run()` calls `_run_components(mi, clock, firsts, lasts, comp_clocks)` with flat lists of firsts, lasts, and comp_clocks. How to handle this with recursive component structure? + + * Aggregate from the bottom up building `_firsts` and `_lasts` in each `MetaComponent` holding the values for its sub-component. + + * Also store the `MetaComponent`'s own summary `first` and `last` which are just `min(firsts)` and `max(lasts)`, respectively. + +* What about `clocks`? The main question is _Which function advances the clock?_ + + * Currently, the `ModelInstance` advances the global clock, and each `ComponentInstance` advances its own clock. + * Simplest to treat the same as `firsts` and `lasts`: build a list of clocks for each `MetaComponentInstance` that is passed down when iterating over sub-components. Each component advances it's own clock after processing its children. + + ### Other stuff * This is a good opportunity to reconsider the treatment of external parameters. The main question is about naming these and whether they need to be globally unique or merely unique within a (meta) component. diff --git a/src/core/metamodel.jl b/src/core/metamodel.jl index 4d904d776..94cce303d 100644 --- a/src/core/metamodel.jl +++ b/src/core/metamodel.jl @@ -1,4 +1,14 @@ -# Create variants for *Instance and *Def +# Convert a list of args with optional type specs to just the arg symbols +_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] + +macro delegate(ex) + if @capture(ex, fname_(varname_::T_, args__) => rhs_) + argnames = _arg_names(args) + result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) + return result + end + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") +end abstract struct AbstractComponentDef <: NamedDef end @@ -56,6 +66,28 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com init::Union{Void, Function} # use same implementation here? run_timestep::Union{Void, Function} + + function LeafComponentInstance{TV, TP}( + comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} + + self = new{TV, TP}() + self.comp_id = comp_id = comp_def.comp_id + self.comp_name = name + self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.variables = vars + self.parameters = pars + self.first = comp_def.first + self.last = comp_def.last + + comp_module = eval(Main, comp_id.module_name) + + # the try/catch allows components with no run_timestep function (as in some of our test cases) + self.run_timestep = func = try eval(comp_module, Symbol("run_timestep_$(comp_id.module_name)_$(comp_id.comp_name)")) end + + return self + end end struct MetaComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance @@ -65,97 +97,81 @@ struct MetaComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentIn # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. leaf::LeafComponentInstance{TV, TP} comps::Vector{AbstractComponentInstance} + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} + clocks::Union{Void, Vector{Clock{T}}} + + function MetaComponentInstance{TV, TP}( + comp_def::MetaComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = new{TV, TP}() + self.leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name) + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + self.clocks = nothing + end end components(obj::MetaComponentInstance) = obj.comps mutable struct ModelInstance md::ModelDef - comp::Union{Void, MetaComponentInstance} - # Push these down to comp? - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - function ModelInstance(md::ModelDef) self = new() self.md = md self.comp = nothing - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() return self end end # If using composition with LeafComponentInstance, we just delegate from # MetaComponentInstance to the internal (summary) LeafComponentInstance. -compid(c::LeafComponentInstance) = c.comp_id -name(c::LeafComponentInstance) = c.comp_name -dims(c::LeafComponentInstance) = c.dim_dict -variables(c::LeafComponentInstance) = c.variables -parameters(c::LeafComponentInstance) = c.parameters - -# Convert a list of args with optional type specs to just the arg symbols -_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] - -macro delegate(ex) - if @capture(ex, fname_(varname_::T_, args__) => rhs_) - argnames = _arg_names(args) - result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) - return result - end - error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") -end - -@delegate compid(c::MetaComponentInstance) => leaf -@delegate name(c::MetaComponentInstance) => leaf -@delegate dims(c::MetaComponentInstance) => leaf -@delegate variables(c::LeafComponentInstance) => leaf -@delegate parameters(c::LeafComponentInstance) => leaf +compid(ci::LeafComponentInstance) = ci.comp_id +name(ci::LeafComponentInstance) = ci.comp_name +dims(ci::LeafComponentInstance) = ci.dim_dict +variables(ci::LeafComponentInstance) = ci.variables +parameters(ci::LeafComponentInstance) = ci.parameters +init_func(ci::LeafComponentInstance) = ci.init +timestep_func(ci::LeafComponentInstance) = ci.run_timestep + +@delegate compid(ci::MetaComponentInstance) => leaf +@delegate name(ci::MetaComponentInstance) => leaf +@delegate dims(ci::MetaComponentInstance) => leaf +@delegate variables(ci::MetaComponentInstance) => leaf +@delegate parameters(ci::MetaComponentInstance) => leaf +@delegate init_func(ci::LeafComponentInstance) => leaf +@delegate timestep_func(ci::MetaComponentInstance) => leaf @delegate variables(mi::ModelInstance) => comp @delegate parameters(mi::ModelInstance) => comp @delegate components(mi::ModelInstance) => comp - - -build(c::Component) = nothing - -function build(m::Model) - for c in components(m) - (vars, pars, dims) = build(c) - - # Add vars, pars, dims to our list - end - - m.mi = build(m.md) - - return # vars, pars, dims -end - - -reset(c::Component) = reset(c.ci) +@delegate firsts(mi::ModelInstance) => comp +@delegate lasts(mi::ModelInstance) => comp +@delegate clocks(mi::ModelInstance) => comp function reset(mci::MetaComponentInstance) for c in components(mci) reset(c) - end + end + return nothing end function run_timestep(ci::AbstractComponentInstance, t::AbstractTimestep) - if ci.run_timestep != nothing - ci.run_timestep(parameters(ci), variables(ci), dims(ci), t) + fn = timestep_func(ci) + if fn != nothing + fn(parameters(ci), variables(ci), dims(ci), t) end - - nothing + return nothing end # This function is called by the default run_timestep defined by @defcomp when # the user defines sub-components and doesn't define an explicit run_timestep. function _meta_run_timestep(p, v, d, t) for ci in components(mci) - ci.run_timestep(ci, t) + run_timestep(ci, t) end - - nothing + return nothing end \ No newline at end of file From 13d62e808477d02d26f33f5937c1f32a967ba7da Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 12 Sep 2018 16:31:56 -0700 Subject: [PATCH 05/81] WIP - Notes on MetaComponents --- docs/src/figs/Mimi-model-schematic-v3.png | Bin 0 -> 118724 bytes docs/src/internals/composite_components.md | 38 ++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 docs/src/figs/Mimi-model-schematic-v3.png diff --git a/docs/src/figs/Mimi-model-schematic-v3.png b/docs/src/figs/Mimi-model-schematic-v3.png new file mode 100644 index 0000000000000000000000000000000000000000..77d79e1d01745621c615f11119295ec5e2be48b7 GIT binary patch literal 118724 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

Esx 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 Zhoe|1f23pw&f~!!B{?x literal 0 HcmV?d00001 diff --git a/docs/src/internals/composite_components.md b/docs/src/internals/composite_components.md index f6a60eb07..357d59b98 100644 --- a/docs/src/internals/composite_components.md +++ b/docs/src/internals/composite_components.md @@ -7,12 +7,11 @@ In Mimi v0.4, we have two levels of model elements, (i) `Model` and (ii) `Compon ## Major elements This suggests three types of elements: -1. `LeafComponent` -- equivalent to the Mimi v0.4 `Component` concept. +1. `LeafComponent(Def|Instance)` -- equivalent to the Mimi v0.4 `Component(Def|Instance)` concept. -1. `MetaComponent` -- presents the same API as a `LeafComponent`, but the variables and parameters it exposes are the aggregated sets of variables and parameters exposed by its components, each of which can be a `MetaComponent` or `LeafComponent`. A `MetaComponent` creates no new storage for variables and parameters; it references the storage in its internal components. The `run_timestep` method of a `MetaComponent` simply calls the `run_timestep` method of each of its internal components in order. - -1. `Model` -- Like a `MetaComponent`, a model contains one or more instances of `MetaComponent` or `LeafComponent`. However, the API for a `Model` differs from that of a `MetaComponent`, thus they are separate classes. For example, you can run a Monte Carlo simulation on a `Model`, but not on a component (of either type). +1. `MetaComponent(Def|Instance)` -- presents the same API as a `LeafComponent(Def|Instance)`, but the variables and parameters it exposes are the aggregated sets of variables and parameters exposed by its components, each of which can be a `MetaComponent` or `LeafComponent`. A `MetaComponentInstance` creates no new storage for variables and parameters; it references the storage in its internal components. By default, the `run_timestep` method of a `MetaComponentInstance` simply calls the `run_timestep` method of each of its internal components in dependency order. +1. `Model` -- Contains a top-level `MetaComponentInstance` that holds all the actual user-defined components, which are instances of `MetaComponentInstance` or `LeafComponentInstance`. The API for `Model` delegates some calls to its top-level `MetaComponentInstance` while providing additional functionality including running a Monte Carlo simulation. ## Implementation Notes @@ -22,9 +21,13 @@ This suggests three types of elements: * As with the currently defined (but not exported) `@defmodel`, component ordering will be determined automatically based on defined connections, with loops avoided by referencing timestep `[t-1]`. This simplifies the API for `addcomponent!`. -* We will add support for two optional functions defined inside `@defmodel`: `before_run` and `after_run`, which are called before and after (respectively) the model is run over all its timesteps. +* We will add support for two optional functions defined inside `@defmodel`: + * `before(m::Model)`, called before the model runs its first timestep + * `after(m:Model)`, called after the model runs its final timestep. + +* A `Model` will be implemented as a wrapper around a single top-level `MetaComponent` that handles the ordering and iteration over sub-components. (In an OOP language, `Model` would subclass `MetaComponent`, but in Julia, we use composition.) -* A `Model` will be implemented as a wrapper around a single top-level `MetaComponent` that handles the ordering and iteration over sub-components. (In an OOP language, `Model` would subclass `MetaComponent`.) +![MetaComponent Schematic](../figs/Mimi-model-schematic-v3.png) ### MetaComponent @@ -35,26 +38,23 @@ This suggests three types of elements: * A `MetaComponent`'s `run_timestep` function is optional. The default function simply calls `run_timestep(subcomps::Vector)` to iterate over sub-components and calls `run_timestep` on each. If a `MetaComponent` defines its own `run_timestep` function, it should either call `run_timestep` on the vector of sub-components or perform a variant of this function itself. -## Questions +* The `@defcomp` macro allows definition of an optional `init` method. To this, we will add support for an `after` method as in `@defmodel`. We will allow `before` as an alias for `init` (perhaps with a deprecation) for consistency with `@defmodel`. -* Currently, `run()` calls `_run_components(mi, clock, firsts, lasts, comp_clocks)` with flat lists of firsts, lasts, and comp_clocks. How to handle this with recursive component structure? +## Other Notes - * Aggregate from the bottom up building `_firsts` and `_lasts` in each `MetaComponent` holding the values for its sub-component. +* Currently, `run()` calls `_run_components(mi, clock, firsts, lasts, comp_clocks)` with simple vectors of firsts, lasts, and comp_clocks. To handle this with the recursive component structure: - * Also store the `MetaComponent`'s own summary `first` and `last` which are just `min(firsts)` and `max(lasts)`, respectively. + * Aggregate from the bottom up building `_firsts` and `_lasts` in each `MetaComponentInstance` holding the values for its sub-components. -* What about `clocks`? The main question is _Which function advances the clock?_ + * Also store the `MetaComponentInstance`'s own summary `first` and `last` which are just `min(firsts)` and `max(lasts)`, respectively. - * Currently, the `ModelInstance` advances the global clock, and each `ComponentInstance` advances its own clock. - * Simplest to treat the same as `firsts` and `lasts`: build a list of clocks for each `MetaComponentInstance` that is passed down when iterating over sub-components. Each component advances it's own clock after processing its children. +* Currently, the `run()` function creates a vector of `Clock` instances, corresponding to each model component. I see two options here: + 1. Extend the current approach to have each `MetaComponentInstance` hold a vector of `Clock` instances for its sub-components. -### Other stuff + 2. Store a `Clock` instance with each `MetaComponentInstance` or `LeafComponentInstance` and provide a recursive method to reset all clocks. -* This is a good opportunity to reconsider the treatment of external parameters. The main question is about naming these and whether they need to be globally unique or merely unique within a (meta) component. -* It turns out that generic functions and dynamic dispatch are not optimal for all design cases. Specifically: - * The case of iterating over a vector of heterogenous objects and calling a function on each is handled poorly with dynamic dispatch. In Mimi, we generate unique functions for these and store them in a pointer, OOP style, so we can call them directly without the cost of dynamic dispatch. This, too, could be handled in a more automated fashion via an OOP macro. +### Other stuff -* The lack of inheritance requires code duplication in the cases where multiple types share the same structure or a portion thereof. An OOP macro could handle this by generating the duplicate structure in two types that share the same abstract type, allowing a single point of modification for the shared elements. - * This could be handled with composition, i.e., defined shared type and have an instance of it in each of the types that share this structure. The extra layer should disappear after compilation. +* This might be is a good time to reconsider the implementation of external parameters. The main question is about naming these and whether they need to be globally unique or merely unique within a (meta) component. From 85419a2d3e6b2ca5da0edee37c2ab81b55d525ee Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 28 Sep 2018 15:46:35 -0700 Subject: [PATCH 06/81] WIP - continue design work --- docs/src/internals/composite_components.md | 22 ++ src/core/composite.jl | 268 +++++++++++++++++++++ src/core/defcc.jl | 57 +++++ src/core/metamodel.jl | 177 -------------- 4 files changed, 347 insertions(+), 177 deletions(-) create mode 100644 src/core/composite.jl create mode 100644 src/core/defcc.jl delete mode 100644 src/core/metamodel.jl diff --git a/docs/src/internals/composite_components.md b/docs/src/internals/composite_components.md index 357d59b98..c1f6dc07a 100644 --- a/docs/src/internals/composite_components.md +++ b/docs/src/internals/composite_components.md @@ -58,3 +58,25 @@ This suggests three types of elements: ### Other stuff * This might be is a good time to reconsider the implementation of external parameters. The main question is about naming these and whether they need to be globally unique or merely unique within a (meta) component. + +* Unit conversion components should be simple "multiplier" components that are bound with specific conversion factors, conceptually like a "closure" on a component. + +* An "identity" component takes an input (external input, bound constant) and allows multiple components to access it. One issue is how to handle the type of argument. Could function wrappers be useful here? + * Identity is simply a unit conversion of 1. + +* If > 1 component exports parameters of the same name, it's an error. At least one comp must rename. + +* David suggested making composite comps immutable, generating a new one each time a change is made. (Why not just have the CompositeComponentInstance be immutable?) + +## Notes from 9/21/18 Meeting + +``` +@defcomp foo begin + Component(bar; export=[var_1, var_2, param_1]) + Component(Mimi.adder, comp_1; # rename locally as :comp_1 + bind=[par_3 => 5, # set a parameter to a fixed value + par_4 => bar.var_1]) # connect a parameter to a variable + + +end +``` \ No newline at end of file diff --git a/src/core/composite.jl b/src/core/composite.jl new file mode 100644 index 000000000..c0938028b --- /dev/null +++ b/src/core/composite.jl @@ -0,0 +1,268 @@ +# Convert a list of args with optional type specs to just the arg symbols +_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] + +# TBD: move this to a more central location +""" +Macro to define a method that simply delegate to a method with the same signature +but using the specified field name of the original first argument as the first arg +in the delegated call. That is, + + `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` + +expands to: + + `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` +""" +macro delegate(ex) + if @capture(ex, fname_(varname_::T_, args__) => rhs_) + argnames = _arg_names(args) + result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) + return result + end + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") +end + + +abstract struct AbstractComponentDef <: NamedDef end + +mutable struct LeafComponentDef <: AbstractComponentDef + name::Symbol + comp_id::ComponentId + variables::OrderedDict{Symbol, DatumDef} + parameters::OrderedDict{Symbol, DatumDef} + dimensions::OrderedDict{Symbol, DimensionDef} + first::Int + last::Int +end + +# *Def implementation doesn't need to be performance-optimized since these +# are used only to create *Instance objects that are used at run-time. With +# this in mind, we don't create dictionaries of vars, params, or dims in the +# MetaComponentDef since this would complicate matters if a user decides to +# add/modify/remove a component. Instead of maintaining a secondary dict, we +# just iterate over sub-components at run-time as needed. + +global const BindingTypes = Union{Int, Float64, Tuple{ComponentId, Symbol}} + +struct CompositeComponentDef <: AbstractComponentDef + comp_id::ComponentId + name::Symbol + comps::Vector{AbstractComponent} + bindings::Vector{Pair{Symbol, BindingTypes}} + exports::Vector{Pair{Symbol, Tuple{ComponentId, Symbol}}} +end + + +# This function is called by the default run_timestep defined by @defcomp when +# the user defines sub-components and doesn't define an explicit run_timestep. +# Can it also be called by the user's run_timestep? +function _composite_run_timestep(p, v, d, t) + for ci in components(mci) + run_timestep(ci, t) + end + return nothing +end + +abstract struct AbstractComponentInstance end + +mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + comp_name::Symbol + comp_id::ComponentId + variables::TV + parameters::TP + dim_dict::Dict{Symbol, Vector{Int}} + + first::Int + last::Int + + init::Union{Void, Function} # use same implementation here? + run_timestep::Union{Void, Function} + + function LeafComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, name::Symbol=name(comp_def); + is_composite::Bool=false) where {TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} + self = new{TV, TP}() + self.comp_id = comp_id = comp_def.comp_id + self.comp_name = name + self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.variables = vars + self.parameters = pars + self.first = comp_def.first + self.last = comp_def.last + + comp_module = eval(Main, comp_id.module_name) + + # the try/catch allows components with no run_timestep function (as in some of our test cases) + if is_composite + self.run_timestep = _composite_run_timestep + else + self.run_timestep = try eval(comp_module, Symbol("run_timestep_$(comp_id.module_name)_$(comp_id.comp_name)")) end + end + + return self + end +end + +struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + + # TV, TP, and dim_dict are computed by aggregating all the vars and params from the CompositeComponent's + # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the + # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. + leaf::LeafComponentInstance{TV, TP} + comps::Vector{AbstractComponentInstance} + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} + clocks::Union{Void, Vector{Clock{T}}} + + function CompositeComponentInstance{TV, TP}( + comp_def::CompositeComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = new{TV, TP}() + self.leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name) + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + self.clocks = nothing + end +end + +components(obj::CompositeComponentInstance) = obj.comps + +struct Model + ccd::CompositeComponentDef + cci::union{Void, CompositeComponentInstance} + + function Model(cc::CompositeComponentDef) + return new(cc, nothing) + end +end + +# If using composition with LeafComponentInstance, we just delegate from +# CompositeComponentInstance to the internal (summary) LeafComponentInstance. +compid(ci::LeafComponentInstance) = ci.comp_id +name(ci::LeafComponentInstance) = ci.comp_name +dims(ci::LeafComponentInstance) = ci.dim_dict +variables(ci::LeafComponentInstance) = ci.variables +parameters(ci::LeafComponentInstance) = ci.parameters +init_func(ci::LeafComponentInstance) = ci.init +timestep_func(ci::LeafComponentInstance) = ci.run_timestep + +@delegate compid(ci::CompositeComponentInstance) => leaf +@delegate name(ci::CompositeComponentInstance) => leaf +@delegate dims(ci::CompositeComponentInstance) => leaf +@delegate variables(ci::CompositeComponentInstance) => leaf +@delegate parameters(ci::CompositeComponentInstance) => leaf +@delegate init_func(ci::LeafComponentInstance) => leaf +@delegate timestep_func(ci::CompositeComponentInstance) => leaf + +@delegate variables(m::Model) => cci +@delegate parameters(m::Model) => cci +@delegate components(m::Model) => cci +@delegate firsts(m::Model) => cci +@delegate lasts(m::Model) => cci +@delegate clocks(m::Model) => cci + +function reset(cci::CompositeComponentInstance) + for c in components(mci) + reset(c) + end + return nothing +end + +function run_timestep(ci::C, t::T) where {C <: AbstractComponentInstance, T <: AbstractTimestep} + fn = timestep_func(ci) + if fn != nothing + fn(parameters(ci), variables(ci), dims(ci), t) + end + return nothing +end + + +""" + defcomposite(cc_name::Symbol, ex::Expr) + +Define a Mimi CompositeComponent `ccc_name` with the expressions in `ex`. Expressions +are all variations on `component(...)`, which adds a component to the composite. The +calling signature for `component` is: + + `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; + export::Union{Void,Vector}, bind::Union{Void,Vector{Pair}})` + +In this macro, the vector of symbols to export is expressed without the `:`, e.g., +`export=[var_1, var_2, param_1])`. The names must be variable or parameter names in +the component being added. + +Bindings are expressed as a vector of `Pair` objects, where the first element of the +pair is a symbol (without the `:` prefix) representing a parameter in the component +being added, and the second element is either a numeric constant, a matrix of the +appropriate shape, or the name of a variable in another component. The variable name +is expressed as the component id (which may be prefixed by a module, e.g., `Mimi.adder`) +followed by a `.` and the variable name in that component. So the form is either +`modname.compname.varname` or `compname.varname`, which must be known in the current module. + +Unlike LeafComponents, CompositeComponents do not have user-defined `init` and `run_timestep` +functions; these are defined internally to simply iterate over constituent components and +call the associated method on each. +""" +macro defcomposite(cc_name, ex) + @capture(ex, elements__) + + result = :( + # @__MODULE__ is evaluated in calling module when macro is interpreted + let calling_module = @__MODULE__ #, comp_mod_name = nothing + global $cc_name = CompositeComponentDef() + end + ) + + # helper function used in loop below + function addexpr(expr) + let_block = result.args[end].args + push!(let_block, expr) + end + + for elt in elements + offset = 0 + + if @capture(elt, component(comp_mod_name_name_.comp_name_) | component(comp_name_) | + component(comp_mod_name_.comp_name_, alias_) | component(comp_name_, alias_)) + + # set local copy of comp_mod_name to the stated or default component module + expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) + addexpr(expr) + + name = (alias === nothing ? comp_name : alias) + expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) + + # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross + elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || + @capture(elt, src_comp_.src_name_ => dst_comp_.dst_name_)) + if (arg !== nothing && (! @capture(arg, t - offset_) || offset <= 0)) + error("Subscripted connection source must have subscript [t - x] where x is an integer > 0") + end + + expr = :(Mimi.connect_param!($cc_name, + $(QuoteNode(dst_comp)), $(QuoteNode(dst_name)), + $(QuoteNode(src_comp)), $(QuoteNode(src_name)), + offset=$offset)) + + elseif @capture(elt, index[idx_name_] = rhs_) + expr = :(Mimi.set_dimension!($cc_name, $(QuoteNode(idx_name)), $rhs)) + + elseif @capture(elt, comp_name_.param_name_ = rhs_) + expr = :(Mimi.set_param!($cc_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) + + else + # Pass through anything else to allow the user to define intermediate vars, etc. + println("Passing through: $elt") + expr = elt + end + + expr = :(CompositeComponentDef($comp_id, $comp_name, $comps, bindings=$bindings, exports=$exports)) + addexpr(expr) + end + + + # addexpr(:($cc_name)) # return this or nothing? + addexpr(:(nothing)) + return esc(result) +end diff --git a/src/core/defcc.jl b/src/core/defcc.jl new file mode 100644 index 000000000..126d0ebe9 --- /dev/null +++ b/src/core/defcc.jl @@ -0,0 +1,57 @@ +macro defcc(name, ex) + legal_kw = (:bindings, :exports) + + @capture(ex, elements__) + println(elements) + for el in elements + if ( @capture(el, component(args__; kwargs__)) || @capture(el, component(args__)) ) + if kwargs === nothing + # extract kw args if expr didn't use a ";" + kwargs = filter(arg -> @capture(arg, lhs_ = rhs__), args) + + # remove the kw args, leaving non-kwargs + filter!(arg -> !@capture(arg, lhs_ = rhs__), args) + end + end + @info "args:$args kwargs:$kwargs" + + nargs = length(args) + if !(nargs == 1 || nargs == 2) + @error "defcc: component takes one or two non-keyword args, got: $args" + end + + num_kwargs = length(kwargs) + if num_kwargs > length(legal_kw) + @error "defcc: component takes one or two non-keyword args, got: $args" + end + + # initialize dict with empty vectors for each keyword, allowing keywords to + # appear multiple times, with all values appended together. + kwdict = Dict([kw => [] for kw in legal_kw]) + + for kwarg in kwargs + @info "kwarg: $kwarg" + @capture(kwarg, lhs_ = rhs__) # we've ensured these match + if ! (lhs in legal_kw) + @error "defcc: Unrecognized keyword $lhs" + end + + append!(kwdict[lhs], rhs) + for kw in keys(kwdict) + val = kwdict[kw] + @info "$kw: $val" + end + end + + id = args[1] + name = (nargs == 2 ? args[2] : nothing) + + end +end + +@defcc foo begin + component(foo, bar; bindings=[1], exports=[1, 2]) + component(foo2, bar2, exports=[1, 2]) + component(bar, other) + # component(a, b, c) +end \ No newline at end of file diff --git a/src/core/metamodel.jl b/src/core/metamodel.jl deleted file mode 100644 index 94cce303d..000000000 --- a/src/core/metamodel.jl +++ /dev/null @@ -1,177 +0,0 @@ -# Convert a list of args with optional type specs to just the arg symbols -_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] - -macro delegate(ex) - if @capture(ex, fname_(varname_::T_, args__) => rhs_) - argnames = _arg_names(args) - result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) - return result - end - error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") -end - -abstract struct AbstractComponentDef <: NamedDef end - -mutable struct LeafComponentDef <: AbstractComponentDef - name::Symbol - comp_id::ComponentId - variables::OrderedDict{Symbol, DatumDef} - parameters::OrderedDict{Symbol, DatumDef} - dimensions::OrderedDict{Symbol, DimensionDef} - first::Int - last::Int -end - -# *Def implementation doesn't need to be performance-optimized since these -# are used only to create *Instance objects that are used at run-time. With -# this in mind, we don't create dictionaries of vars, params, or dims in the -# MetaComponentDef since this would complicate matters if a user decides to -# add/modify/remove a component. Instead of maintaining a secondary dict, we -# just iterate over sub-components at run-time as needed. - -struct MetaComponentDef <: AbstractComponentDef - name::Symbol - comp_id::ComponentId - comps::Vector{AbstractComponent} -end - -struct Model - metacomp::MetaComponent - mi::union{ModelInstance, Void} - md::ModelDef - - function new(md::ModelDef) - m = Model(md) - m.mi = nothing - m.comps = Vector{AbstractComponent}() # compute these when the model is built - m.pars = Vector{ParameterInstance}() - m.vars = Vector{VariableInstance}() - m.vars = Vector{VariableInstance}() - return m - end -end - - -abstract struct AbstractComponentInstance end - -mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance - comp_name::Symbol - comp_id::ComponentId - variables::TV - parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} - - first::Int - last::Int - - init::Union{Void, Function} # use same implementation here? - run_timestep::Union{Void, Function} - - function LeafComponentInstance{TV, TP}( - comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} - - self = new{TV, TP}() - self.comp_id = comp_id = comp_def.comp_id - self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage - self.variables = vars - self.parameters = pars - self.first = comp_def.first - self.last = comp_def.last - - comp_module = eval(Main, comp_id.module_name) - - # the try/catch allows components with no run_timestep function (as in some of our test cases) - self.run_timestep = func = try eval(comp_module, Symbol("run_timestep_$(comp_id.module_name)_$(comp_id.comp_name)")) end - - return self - end -end - -struct MetaComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance - - # TV, TP, and dim_dict are computed by aggregating all the vars and params from the MetaComponent's - # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the - # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. - leaf::LeafComponentInstance{TV, TP} - comps::Vector{AbstractComponentInstance} - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - clocks::Union{Void, Vector{Clock{T}}} - - function MetaComponentInstance{TV, TP}( - comp_def::MetaComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = new{TV, TP}() - self.leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name) - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - self.clocks = nothing - end -end - -components(obj::MetaComponentInstance) = obj.comps - -mutable struct ModelInstance - md::ModelDef - comp::Union{Void, MetaComponentInstance} - - function ModelInstance(md::ModelDef) - self = new() - self.md = md - self.comp = nothing - return self - end -end - -# If using composition with LeafComponentInstance, we just delegate from -# MetaComponentInstance to the internal (summary) LeafComponentInstance. -compid(ci::LeafComponentInstance) = ci.comp_id -name(ci::LeafComponentInstance) = ci.comp_name -dims(ci::LeafComponentInstance) = ci.dim_dict -variables(ci::LeafComponentInstance) = ci.variables -parameters(ci::LeafComponentInstance) = ci.parameters -init_func(ci::LeafComponentInstance) = ci.init -timestep_func(ci::LeafComponentInstance) = ci.run_timestep - -@delegate compid(ci::MetaComponentInstance) => leaf -@delegate name(ci::MetaComponentInstance) => leaf -@delegate dims(ci::MetaComponentInstance) => leaf -@delegate variables(ci::MetaComponentInstance) => leaf -@delegate parameters(ci::MetaComponentInstance) => leaf -@delegate init_func(ci::LeafComponentInstance) => leaf -@delegate timestep_func(ci::MetaComponentInstance) => leaf - -@delegate variables(mi::ModelInstance) => comp -@delegate parameters(mi::ModelInstance) => comp -@delegate components(mi::ModelInstance) => comp -@delegate firsts(mi::ModelInstance) => comp -@delegate lasts(mi::ModelInstance) => comp -@delegate clocks(mi::ModelInstance) => comp - -function reset(mci::MetaComponentInstance) - for c in components(mci) - reset(c) - end - return nothing -end - -function run_timestep(ci::AbstractComponentInstance, t::AbstractTimestep) - fn = timestep_func(ci) - if fn != nothing - fn(parameters(ci), variables(ci), dims(ci), t) - end - return nothing -end - -# This function is called by the default run_timestep defined by @defcomp when -# the user defines sub-components and doesn't define an explicit run_timestep. -function _meta_run_timestep(p, v, d, t) - for ci in components(mci) - run_timestep(ci, t) - end - return nothing -end \ No newline at end of file From 0533eb8cd7b592d8891947407c797b574737be7a Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 1 Oct 2018 08:53:25 -0700 Subject: [PATCH 07/81] WIP - developing new macro --- src/core/defcc.jl | 88 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/src/core/defcc.jl b/src/core/defcc.jl index 126d0ebe9..b41c27d25 100644 --- a/src/core/defcc.jl +++ b/src/core/defcc.jl @@ -1,6 +1,57 @@ -macro defcc(name, ex) +using MacroTools + +# Dummy versions for macro testing +struct ComponentId + module_name::Symbol + comp_name::Symbol +end + +abstract type AbstractComponentDef end + +struct BindingDef + param::Symbol + value::Union{Number, Symbol} +end + +mutable struct CompositeComponentDef <: AbstractComponentDef + comp_id::ComponentId + name::Symbol + comps::Vector{AbstractComponentDef} + bindings::Vector{BindingDef} + exports::Vector{Symbol} + + function CompositeComponentDef(module_name::Symbol, name::Symbol) + compid = ComponentId(module_name, name) + comps = Vector{AbstractComponentDef}() + bindings = Vector{BindingDef}() + exports = Vector{Symbol}() + return new(compid, name, comps, bindings, exports) + end +end + +function add_comp!(cc::CompositeComponentDef, compid::ComponentId, name::Symbol, bindings::Vector{BindingDef}, exports::Vector{Symbol}) + # push!(cc.comps, comp) + # append!(cc.bindings, bindings) + # append!(cc.exports, exports) +end + +macro defcc(cc_name, ex) legal_kw = (:bindings, :exports) + # @__MODULE__ is evaluated in calling module when macro is interpreted + result = :( + let calling_module = @__MODULE__ + global $cc_name = CompositeComponentDef($(QuoteNode(cc_name))) + end + ) + + # helper function used in loop below + function addexpr(expr) + let_block = result.args[end].args + @info "addexpr($expr)" + push!(let_block, expr) + end + @capture(ex, elements__) println(elements) for el in elements @@ -17,12 +68,12 @@ macro defcc(name, ex) nargs = length(args) if !(nargs == 1 || nargs == 2) - @error "defcc: component takes one or two non-keyword args, got: $args" + error("defcc: component takes one or two non-keyword args, got: $args") end num_kwargs = length(kwargs) if num_kwargs > length(legal_kw) - @error "defcc: component takes one or two non-keyword args, got: $args" + error("defcc: component takes one or two non-keyword args, got: $args") end # initialize dict with empty vectors for each keyword, allowing keywords to @@ -31,27 +82,42 @@ macro defcc(name, ex) for kwarg in kwargs @info "kwarg: $kwarg" - @capture(kwarg, lhs_ = rhs__) # we've ensured these match + @capture(kwarg, lhs_ = rhs__) || error("defcc: keyword arg '$kwarg' is missing a value") + if ! (lhs in legal_kw) - @error "defcc: Unrecognized keyword $lhs" + error("defcc: unrecognized keyword $lhs") end append!(kwdict[lhs], rhs) - for kw in keys(kwdict) - val = kwdict[kw] - @info "$kw: $val" - end + + # for kw in keys(kwdict) + # val = kwdict[kw] + # @info "$kw: $val" + # end end id = args[1] - name = (nargs == 2 ? args[2] : nothing) + name = (nargs == 2 ? args[2] : nothing) + expr = :(add_comp!($cc_name, $id, $name; bindings=$(kwdict[:bindings]), exports=$(kwdict[:exports]))) + addexpr(expr) end + + addexpr(:(nothing)) + return esc(result) end -@defcc foo begin +@macroexpand @defcc my_cc begin component(foo, bar; bindings=[1], exports=[1, 2]) component(foo2, bar2, exports=[1, 2]) component(bar, other) + + # error: "defcc: component takes one or two non-keyword args" # component(a, b, c) + + # error: "defcc: unrecognized keyword unrecog" + # component(foo, bar, unrecog=[1, 2, 3]) + + # error: "defcc: keyword arg 'baz' is missing a value" + # component(foo, bar; baz) end \ No newline at end of file From fda38cbbd34de30c415301043310e531ac44fe7c Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sun, 21 Oct 2018 23:14:15 -0700 Subject: [PATCH 08/81] WIP - composite components --- src/core/composite.jl | 202 +++++++++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 62 deletions(-) diff --git a/src/core/composite.jl b/src/core/composite.jl index c0938028b..7a8851e99 100644 --- a/src/core/composite.jl +++ b/src/core/composite.jl @@ -52,17 +52,6 @@ struct CompositeComponentDef <: AbstractComponentDef exports::Vector{Pair{Symbol, Tuple{ComponentId, Symbol}}} end - -# This function is called by the default run_timestep defined by @defcomp when -# the user defines sub-components and doesn't define an explicit run_timestep. -# Can it also be called by the user's run_timestep? -function _composite_run_timestep(p, v, d, t) - for ci in components(mci) - run_timestep(ci, t) - end - return nothing -end - abstract struct AbstractComponentInstance end mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance @@ -92,13 +81,11 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com comp_module = eval(Main, comp_id.module_name) - # the try/catch allows components with no run_timestep function (as in some of our test cases) - if is_composite - self.run_timestep = _composite_run_timestep - else - self.run_timestep = try eval(comp_module, Symbol("run_timestep_$(comp_id.module_name)_$(comp_id.comp_name)")) end - end - + # The try/catch allows components with no run_timestep function (as in some of our test cases) + # All CompositeComponentInstances use a standard method that just loops over inner components. + mod_and_comp = "$(comp_id.module_name)_$(comp_id.comp_name)" + self.run_timestep = is_composite ? nothing else try eval(comp_module, Symbol("run_timestep_$(mod_and_comp)")) end + self.init = is_composite ? nothing else try eval(comp_module, Symbol("init_$(mod_and_comp)")) end return self end end @@ -137,8 +124,7 @@ struct Model end end -# If using composition with LeafComponentInstance, we just delegate from -# CompositeComponentInstance to the internal (summary) LeafComponentInstance. +# We delegate from CompositeComponentInstance to the internal LeafComponentInstance. compid(ci::LeafComponentInstance) = ci.comp_id name(ci::LeafComponentInstance) = ci.comp_name dims(ci::LeafComponentInstance) = ci.dim_dict @@ -152,8 +138,6 @@ timestep_func(ci::LeafComponentInstance) = ci.run_timestep @delegate dims(ci::CompositeComponentInstance) => leaf @delegate variables(ci::CompositeComponentInstance) => leaf @delegate parameters(ci::CompositeComponentInstance) => leaf -@delegate init_func(ci::LeafComponentInstance) => leaf -@delegate timestep_func(ci::CompositeComponentInstance) => leaf @delegate variables(m::Model) => cci @delegate parameters(m::Model) => cci @@ -162,47 +146,123 @@ timestep_func(ci::LeafComponentInstance) = ci.run_timestep @delegate lasts(m::Model) => cci @delegate clocks(m::Model) => cci -function reset(cci::CompositeComponentInstance) - for c in components(mci) - reset(c) +function reset_variables(cci::CompositeComponentInstance) + for ci in components(cci) + reset_variables(ci) end return nothing end -function run_timestep(ci::C, t::T) where {C <: AbstractComponentInstance, T <: AbstractTimestep} +function init(ci::LeafComponentInstance) + reset_variables(ci) + + fn = init_func(ci) + if fn != nothing + fn(parameters(ci), variables(ci), dims(ci)) + end + return nothing +end + +function init(cci::CompositeComponentInstance) + for ci in components(cci) + init(ci) + end + return nothing +end + +function run_timestep(ci::LeafComponentInstance, clock::Clock) fn = timestep_func(ci) if fn != nothing - fn(parameters(ci), variables(ci), dims(ci), t) + fn(parameters(ci), variables(ci), dims(ci), clock.ts) end + + # TBD: move this outside this func if components share a clock + advance(clock) + return nothing end +function run_timestep(cci::CompositeComponentInstance, clock::Clock) + for ci in components(cci) + run_timestep(ci, clock) + end + return nothing +end + + # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) +_arg_name(arg_tup) = arg_tup[1] +_arg_type(arg_tup) = arg_tup[2] +_arg_slurp(arg_tup) = arg_tup[3] +_arg_default(arg_tup) = arg_tup[4] + +function _extract_args(args, kwargs) + valid_kws = (:exports, :bindings) # valid keyword args to `component()` + kw_values = Dict() + + arg_tups = map(splitarg, args) + + if kwargs === nothing + # If a ";" was not used to separate kwargs, extract them from args. + # tup[4] => "default" value which for kwargs, the actual value. + kwarg_tups = filter!(tup -> _arg_default(tup) !== nothing, arg_tups) + else + kwarg_tups = map(splitarg, kwargs) + end + + @info "args: $arg_tups" + @info "kwargs: $kwarg_tups" + + if 1 > length(arg_tups) > 2 + @error "component() must have one or two non-keyword values" + end + + arg1 = _arg_name(arg_tups[1]) + arg2 = length(args) == 2 ? _arg_name(arg_tups[2]) : nothing + + for tup in kwarg_tups + arg_name = _arg_name(tup) + if arg_name in valid_kws + default = _arg_default(tup) + if hasmethod(Base.iterate, (typeof(default),)) + append!(kw_values[arg_name], default) + else + @error "Value of $arg_name argument must be iterable" + end + + else + @error "Unknown keyword $arg_name; valid keywords are $valid_kws" + end + end + + @info "kw_values: $kw_values" + return (arg1, arg2, kw_values) +end """ defcomposite(cc_name::Symbol, ex::Expr) -Define a Mimi CompositeComponent `ccc_name` with the expressions in `ex`. Expressions +Define a Mimi CompositeComponent `cc_name` with the expressions in `ex`. Expressions are all variations on `component(...)`, which adds a component to the composite. The -calling signature for `component` is: +calling signature for `component()` processed herein is: `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; - export::Union{Void,Vector}, bind::Union{Void,Vector{Pair}})` + exports::Union{Void,Vector}, bindings::Union{Void,Vector{Pair}})` In this macro, the vector of symbols to export is expressed without the `:`, e.g., -`export=[var_1, var_2, param_1])`. The names must be variable or parameter names in +`exports=[var_1, var_2, param_1])`. The names must be variable or parameter names in the component being added. Bindings are expressed as a vector of `Pair` objects, where the first element of the -pair is a symbol (without the `:` prefix) representing a parameter in the component +pair is the name (again, without the `:` prefix) representing a parameter in the component being added, and the second element is either a numeric constant, a matrix of the appropriate shape, or the name of a variable in another component. The variable name is expressed as the component id (which may be prefixed by a module, e.g., `Mimi.adder`) followed by a `.` and the variable name in that component. So the form is either `modname.compname.varname` or `compname.varname`, which must be known in the current module. -Unlike LeafComponents, CompositeComponents do not have user-defined `init` and `run_timestep` -functions; these are defined internally to simply iterate over constituent components and -call the associated method on each. +Unlike LeafComponents, CompositeComponents do not have user-defined `init` or `run_timestep` +functions; these are defined internally to iterate over constituent components and call +the associated method on each. """ macro defcomposite(cc_name, ex) @capture(ex, elements__) @@ -220,45 +280,63 @@ macro defcomposite(cc_name, ex) push!(let_block, expr) end + valid_kws = (:exports, :bindings) # valid keyword args to `component()` + kw_values = Dict() + for elt in elements offset = 0 - if @capture(elt, component(comp_mod_name_name_.comp_name_) | component(comp_name_) | - component(comp_mod_name_.comp_name_, alias_) | component(comp_name_, alias_)) + if @capture(elt, (component(args__; kwargs__) | component(args__))) - # set local copy of comp_mod_name to the stated or default component module - expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) - addexpr(expr) + # component(comp_mod_name_.comp_name_) | + # component(comp_mod_name_.comp_name_, alias_))) - name = (alias === nothing ? comp_name : alias) - expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) + # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) + arg_tups = map(splitarg, args) - # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross - elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || - @capture(elt, src_comp_.src_name_ => dst_comp_.dst_name_)) - if (arg !== nothing && (! @capture(arg, t - offset_) || offset <= 0)) - error("Subscripted connection source must have subscript [t - x] where x is an integer > 0") + if kwargs === nothing + # If a ";" was not used to separate kwargs, extract them from args. + # tup[4] => "default" value which for kwargs, the actual value. + kwarg_tups = filter!(tup -> tup[4] !== nothing, arg_tups) + else + kwarg_tups = map(splitarg, kwargs) end - expr = :(Mimi.connect_param!($cc_name, - $(QuoteNode(dst_comp)), $(QuoteNode(dst_name)), - $(QuoteNode(src_comp)), $(QuoteNode(src_name)), - offset=$offset)) + @info "args: $args" + @info "kwargs: $kwargs" - elseif @capture(elt, index[idx_name_] = rhs_) - expr = :(Mimi.set_dimension!($cc_name, $(QuoteNode(idx_name)), $rhs)) + if 1 > length(args) > 2 + @error "component() must have one or two non-keyword values" + end - elseif @capture(elt, comp_name_.param_name_ = rhs_) - expr = :(Mimi.set_param!($cc_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) + arg1 = args[1] + arg2 = length(args) == 2 ? args[2] else nothing - else - # Pass through anything else to allow the user to define intermediate vars, etc. - println("Passing through: $elt") - expr = elt + for (arg_name, arg_type, slurp, default) in kwarg_tups + if arg_name in valid_kws + if hasmethod(Base.iterate, (typeof(default),) + append!(kw_values[arg_name], default) + else + @error "Value of $arg_name argument must be iterable" + end + + else + @error "Unknown keyword $arg_name; valid keywords are $valid_kws" + end + end + + @info "kw_values: $kw_values" + + # set local copy of comp_mod_name to the stated or default component module + expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) + addexpr(expr) + + # name = (alias === nothing ? comp_name : alias) + # expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) + + expr = :(CompositeComponentDef($comp_id, $comp_name, $comps, bindings=$bindings, exports=$exports)) + addexpr(expr) end - - expr = :(CompositeComponentDef($comp_id, $comp_name, $comps, bindings=$bindings, exports=$exports)) - addexpr(expr) end From 5373dcc199d762d38e6b57cd7b24dc1c2c324303 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 23 Oct 2018 08:59:14 -0700 Subject: [PATCH 09/81] WIP -- Composite components checkpoint --- src/Mimi.jl | 3 +- src/core/build.jl | 16 +- src/core/composite.jl | 152 ++-------------- src/core/connections.jl | 2 +- src/core/defcc.jl | 123 ------------- src/core/defcomp.jl | 2 +- src/core/defs.jl | 141 +++++++++----- src/core/model.jl | 66 +++---- src/core/types.jl | 252 +++++++++++++++++--------- test/test_composite.jl | 62 +++++++ test/test_variables_model_instance.jl | 2 +- 11 files changed, 377 insertions(+), 444 deletions(-) delete mode 100644 src/core/defcc.jl create mode 100644 test/test_composite.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index 198e9882e..f51daf839 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -42,8 +42,9 @@ export variables include("core/types.jl") +include("core/composite.jl") -# After loading types, the rest can just be alphabetical +# After loading types and delegation macro, the rest can just be alphabetical include("core/build.jl") include("core/connections.jl") include("core/defs.jl") diff --git a/src/core/build.jl b/src/core/build.jl index 85f7bf0a3..984b92327 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -56,25 +56,13 @@ function _instantiate_datum(md::ModelDef, def::DatumDef) return value end -# Deprecated? -# function _vars_NT_type(md::ModelDef, comp_def::ComponentDef) -# var_defs = variables(comp_def) -# vnames = Tuple([name(vdef) for vdef in var_defs]) - -# first = comp_def.first -# vtypes = Tuple{[_instance_datatype(md, vdef, first) for vdef in var_defs]...} - -# NT = NamedTuple{vnames, vtypes} -# return NT -# end - """ - _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) + _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) Instantiate a component `comp_def` in the model `md` and its variables (but not its parameters). Return the resulting ComponentInstance. """ -function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) +function _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) comp_name = name(comp_def) var_defs = variables(comp_def) diff --git a/src/core/composite.jl b/src/core/composite.jl index 7a8851e99..e903fe82a 100644 --- a/src/core/composite.jl +++ b/src/core/composite.jl @@ -1,138 +1,14 @@ -# Convert a list of args with optional type specs to just the arg symbols -_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] - -# TBD: move this to a more central location -""" -Macro to define a method that simply delegate to a method with the same signature -but using the specified field name of the original first argument as the first arg -in the delegated call. That is, - - `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` - -expands to: - - `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` -""" -macro delegate(ex) - if @capture(ex, fname_(varname_::T_, args__) => rhs_) - argnames = _arg_names(args) - result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) - return result - end - error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") -end - - -abstract struct AbstractComponentDef <: NamedDef end - -mutable struct LeafComponentDef <: AbstractComponentDef - name::Symbol - comp_id::ComponentId - variables::OrderedDict{Symbol, DatumDef} - parameters::OrderedDict{Symbol, DatumDef} - dimensions::OrderedDict{Symbol, DimensionDef} - first::Int - last::Int -end - -# *Def implementation doesn't need to be performance-optimized since these -# are used only to create *Instance objects that are used at run-time. With -# this in mind, we don't create dictionaries of vars, params, or dims in the -# MetaComponentDef since this would complicate matters if a user decides to -# add/modify/remove a component. Instead of maintaining a secondary dict, we -# just iterate over sub-components at run-time as needed. - -global const BindingTypes = Union{Int, Float64, Tuple{ComponentId, Symbol}} - -struct CompositeComponentDef <: AbstractComponentDef - comp_id::ComponentId - name::Symbol - comps::Vector{AbstractComponent} - bindings::Vector{Pair{Symbol, BindingTypes}} - exports::Vector{Pair{Symbol, Tuple{ComponentId, Symbol}}} -end - -abstract struct AbstractComponentInstance end - -mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance - comp_name::Symbol - comp_id::ComponentId - variables::TV - parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} - - first::Int - last::Int - - init::Union{Void, Function} # use same implementation here? - run_timestep::Union{Void, Function} - - function LeafComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, name::Symbol=name(comp_def); - is_composite::Bool=false) where {TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} - self = new{TV, TP}() - self.comp_id = comp_id = comp_def.comp_id - self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage - self.variables = vars - self.parameters = pars - self.first = comp_def.first - self.last = comp_def.last - - comp_module = eval(Main, comp_id.module_name) - - # The try/catch allows components with no run_timestep function (as in some of our test cases) - # All CompositeComponentInstances use a standard method that just loops over inner components. - mod_and_comp = "$(comp_id.module_name)_$(comp_id.comp_name)" - self.run_timestep = is_composite ? nothing else try eval(comp_module, Symbol("run_timestep_$(mod_and_comp)")) end - self.init = is_composite ? nothing else try eval(comp_module, Symbol("init_$(mod_and_comp)")) end - return self - end -end - -struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance - - # TV, TP, and dim_dict are computed by aggregating all the vars and params from the CompositeComponent's - # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the - # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. - leaf::LeafComponentInstance{TV, TP} - comps::Vector{AbstractComponentInstance} - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - clocks::Union{Void, Vector{Clock{T}}} - - function CompositeComponentInstance{TV, TP}( - comp_def::CompositeComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = new{TV, TP}() - self.leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name) - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - self.clocks = nothing - end -end +using MacroTools components(obj::CompositeComponentInstance) = obj.comps -struct Model - ccd::CompositeComponentDef - cci::union{Void, CompositeComponentInstance} - - function Model(cc::CompositeComponentDef) - return new(cc, nothing) - end -end - -# We delegate from CompositeComponentInstance to the internal LeafComponentInstance. compid(ci::LeafComponentInstance) = ci.comp_id name(ci::LeafComponentInstance) = ci.comp_name dims(ci::LeafComponentInstance) = ci.dim_dict variables(ci::LeafComponentInstance) = ci.variables parameters(ci::LeafComponentInstance) = ci.parameters -init_func(ci::LeafComponentInstance) = ci.init -timestep_func(ci::LeafComponentInstance) = ci.run_timestep +# CompositeComponentInstance delegates these to its internal LeafComponentInstance @delegate compid(ci::CompositeComponentInstance) => leaf @delegate name(ci::CompositeComponentInstance) => leaf @delegate dims(ci::CompositeComponentInstance) => leaf @@ -156,9 +32,8 @@ end function init(ci::LeafComponentInstance) reset_variables(ci) - fn = init_func(ci) - if fn != nothing - fn(parameters(ci), variables(ci), dims(ci)) + if ci.init != nothing + ci.init(parameters(ci), variables(ci), dims(ci)) end return nothing end @@ -171,9 +46,8 @@ function init(cci::CompositeComponentInstance) end function run_timestep(ci::LeafComponentInstance, clock::Clock) - fn = timestep_func(ci) - if fn != nothing - fn(parameters(ci), variables(ci), dims(ci), clock.ts) + if ci.run_timestep != nothing + ci.run_timestep(parameters(ci), variables(ci), dims(ci), clock.ts) end # TBD: move this outside this func if components share a clock @@ -245,8 +119,8 @@ Define a Mimi CompositeComponent `cc_name` with the expressions in `ex`. Expres are all variations on `component(...)`, which adds a component to the composite. The calling signature for `component()` processed herein is: - `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; - exports::Union{Void,Vector}, bindings::Union{Void,Vector{Pair}})` + `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; + exports::Union{Nothing,Vector}, bindings::Union{Nothing,Vector{Pair}})` In this macro, the vector of symbols to export is expressed without the `:`, e.g., `exports=[var_1, var_2, param_1])`. The names must be variable or parameter names in @@ -273,7 +147,7 @@ macro defcomposite(cc_name, ex) global $cc_name = CompositeComponentDef() end ) - + # helper function used in loop below function addexpr(expr) let_block = result.args[end].args @@ -288,7 +162,7 @@ macro defcomposite(cc_name, ex) if @capture(elt, (component(args__; kwargs__) | component(args__))) - # component(comp_mod_name_.comp_name_) | + # component(comp_mod_name_.comp_name_) | # component(comp_mod_name_.comp_name_, alias_))) # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) @@ -310,11 +184,11 @@ macro defcomposite(cc_name, ex) end arg1 = args[1] - arg2 = length(args) == 2 ? args[2] else nothing + arg2 = length(args) == 2 ? args[2] : nothing for (arg_name, arg_type, slurp, default) in kwarg_tups if arg_name in valid_kws - if hasmethod(Base.iterate, (typeof(default),) + if hasmethod(Base.iterate, typeof(default)) append!(kw_values[arg_name], default) else @error "Value of $arg_name argument must be iterable" @@ -333,7 +207,7 @@ macro defcomposite(cc_name, ex) # name = (alias === nothing ? comp_name : alias) # expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) - + expr = :(CompositeComponentDef($comp_id, $comp_name, $comps, bindings=$bindings, exports=$exports)) addexpr(expr) end diff --git a/src/core/connections.jl b/src/core/connections.jl index ecc1ae198..86e521ef6 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -19,7 +19,7 @@ function verify_units(one::AbstractString, two::AbstractString) return one == two end -function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) +function _check_labels(md::ModelDef, comp_def::AbstractComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) param_def = parameter(comp_def, param_name) t1 = eltype(ext_param.values) diff --git a/src/core/defcc.jl b/src/core/defcc.jl deleted file mode 100644 index b41c27d25..000000000 --- a/src/core/defcc.jl +++ /dev/null @@ -1,123 +0,0 @@ -using MacroTools - -# Dummy versions for macro testing -struct ComponentId - module_name::Symbol - comp_name::Symbol -end - -abstract type AbstractComponentDef end - -struct BindingDef - param::Symbol - value::Union{Number, Symbol} -end - -mutable struct CompositeComponentDef <: AbstractComponentDef - comp_id::ComponentId - name::Symbol - comps::Vector{AbstractComponentDef} - bindings::Vector{BindingDef} - exports::Vector{Symbol} - - function CompositeComponentDef(module_name::Symbol, name::Symbol) - compid = ComponentId(module_name, name) - comps = Vector{AbstractComponentDef}() - bindings = Vector{BindingDef}() - exports = Vector{Symbol}() - return new(compid, name, comps, bindings, exports) - end -end - -function add_comp!(cc::CompositeComponentDef, compid::ComponentId, name::Symbol, bindings::Vector{BindingDef}, exports::Vector{Symbol}) - # push!(cc.comps, comp) - # append!(cc.bindings, bindings) - # append!(cc.exports, exports) -end - -macro defcc(cc_name, ex) - legal_kw = (:bindings, :exports) - - # @__MODULE__ is evaluated in calling module when macro is interpreted - result = :( - let calling_module = @__MODULE__ - global $cc_name = CompositeComponentDef($(QuoteNode(cc_name))) - end - ) - - # helper function used in loop below - function addexpr(expr) - let_block = result.args[end].args - @info "addexpr($expr)" - push!(let_block, expr) - end - - @capture(ex, elements__) - println(elements) - for el in elements - if ( @capture(el, component(args__; kwargs__)) || @capture(el, component(args__)) ) - if kwargs === nothing - # extract kw args if expr didn't use a ";" - kwargs = filter(arg -> @capture(arg, lhs_ = rhs__), args) - - # remove the kw args, leaving non-kwargs - filter!(arg -> !@capture(arg, lhs_ = rhs__), args) - end - end - @info "args:$args kwargs:$kwargs" - - nargs = length(args) - if !(nargs == 1 || nargs == 2) - error("defcc: component takes one or two non-keyword args, got: $args") - end - - num_kwargs = length(kwargs) - if num_kwargs > length(legal_kw) - error("defcc: component takes one or two non-keyword args, got: $args") - end - - # initialize dict with empty vectors for each keyword, allowing keywords to - # appear multiple times, with all values appended together. - kwdict = Dict([kw => [] for kw in legal_kw]) - - for kwarg in kwargs - @info "kwarg: $kwarg" - @capture(kwarg, lhs_ = rhs__) || error("defcc: keyword arg '$kwarg' is missing a value") - - if ! (lhs in legal_kw) - error("defcc: unrecognized keyword $lhs") - end - - append!(kwdict[lhs], rhs) - - # for kw in keys(kwdict) - # val = kwdict[kw] - # @info "$kw: $val" - # end - end - - id = args[1] - name = (nargs == 2 ? args[2] : nothing) - - expr = :(add_comp!($cc_name, $id, $name; bindings=$(kwdict[:bindings]), exports=$(kwdict[:exports]))) - addexpr(expr) - end - - addexpr(:(nothing)) - return esc(result) -end - -@macroexpand @defcc my_cc begin - component(foo, bar; bindings=[1], exports=[1, 2]) - component(foo2, bar2, exports=[1, 2]) - component(bar, other) - - # error: "defcc: component takes one or two non-keyword args" - # component(a, b, c) - - # error: "defcc: unrecognized keyword unrecog" - # component(foo, bar, unrecog=[1, 2, 3]) - - # error: "defcc: keyword arg 'baz' is missing a value" - # component(foo, bar; baz) -end \ No newline at end of file diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 258b8ac4c..351b6a929 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -120,7 +120,7 @@ types of expressions are supported: 5. `run_timestep(p, v, d, t)` # defines a run_timestep function for the component Parses a @defcomp definition, converting it into a series of function calls that -create the corresponding ComponentDef instance. At model build time, the ModelDef +create the corresponding LeafComponentDef instance. At model build time, the ModelDef (including its ComponentDefs) will be converted to a runnable model. """ macro defcomp(comp_name, ex) diff --git a/src/core/defs.jl b/src/core/defs.jl index 3ab4574ce..eb868029e 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,5 +1,5 @@ # Global component registry: @defcomp stores component definitions here -global const _compdefs = Dict{ComponentId, ComponentDef}() +global const _compdefs = Dict{ComponentId, AbstractComponentDef}() compdefs() = collect(values(_compdefs)) @@ -35,11 +35,14 @@ function reset_compdefs(reload_builtins=true) end end -first_period(comp_def::ComponentDef) = comp_def.first -first_period(md::ModelDef, comp_def::ComponentDef) = first_period(comp_def) === nothing ? time_labels(md)[1] : first_period(comp_def) +first_period(comp_def::LeafComponentDef) = comp_def.first +last_period(comp_def::LeafComponentDef) = comp_def.last -last_period(comp_def::ComponentDef) = comp_def.last -last_period(md::ModelDef, comp_def::ComponentDef) = last_period(comp_def) === nothing ? time_labels(md)[end] : last_period(comp_def) +first_period(ccd::CompositeComponentDef) = length(ccd.comps) ? min([first_period(cd) for cd in ccd.comps]) : nothing +last_period(ccd::CompositeComponentDef) = length(ccd.comps) ? max([last_period(cd) for cd in ccd.comps]) : nothing + +first_period(md::ModelDef, comp_def::AbstractComponentDef) = first_period(comp_def) === nothing ? time_labels(md)[1] : first_period(comp_def) +last_period(md::ModelDef, comp_def::AbstractComponentDef) = last_period(comp_def) === nothing ? time_labels(md)[end] : last_period(comp_def) # Return the module object for the component was defined in compmodule(comp_id::ComponentId) = comp_id.module_name @@ -47,21 +50,22 @@ compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name function Base.show(io::IO, comp_id::ComponentId) - print(io, "$(comp_id.module_name).$(comp_id.comp_name)") + print(io, "") end """ name(def::NamedDef) = def.name -Return the name of `def`. Possible `NamedDef`s include `DatumDef`, `ComponentDef`, -and `DimensionDef`. +Return the name of `def`. Possible `NamedDef`s include `DatumDef`, `LeavComponentDef`, +`CompositeComponentDef`, and `DimensionDef`. """ name(def::NamedDef) = def.name number_type(md::ModelDef) = md.number_type -numcomponents(md::ModelDef) = length(md.comp_defs) +@delegate numcomponents(md::ModelDef) => ccd +numcomponents(ccd::CompositeComponentDef) = length(ccd.comps) function dump_components() for comp in compdefs() @@ -78,9 +82,9 @@ end """ new_comp(comp_id::ComponentId, verbose::Bool=true) -Add an empty `ComponentDef` to the global component registry with the given -`comp_id`. The empty `ComponentDef` must be populated with calls to `addvariable`, -`addparameter`, etc. +Add an empty `LeafComponentDef` to the global component registry with the given +`comp_id`. The empty `LeafComponentDef` must be populated with calls to `addvariable`, +`addparameter`, etc. Use `@defcomposite` to create composite components. """ function new_comp(comp_id::ComponentId, verbose::Bool=true) if verbose @@ -91,7 +95,7 @@ function new_comp(comp_id::ComponentId, verbose::Bool=true) end end - comp_def = ComponentDef(comp_id) + comp_def = LeafComponentDef(comp_id) _compdefs[comp_id] = comp_def return comp_def end @@ -118,18 +122,20 @@ end # # Dimensions # -function add_dimension!(comp::ComponentDef, name) + +# TBD: is this needed for composites too? +function add_dimension!(comp::LeafComponentDef, name) comp.dimensions[name] = dim_def = DimensionDef(name) return dim_def end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -dimensions(comp_def::ComponentDef) = values(comp_def.dimensions) +dimensions(comp_def::LeafComponentDef) = values(comp_def.dimensions) dimensions(def::DatumDef) = def.dimensions -dimensions(comp_def::ComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) +dimensions(comp_def::LeafComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) dim_count(def::DatumDef) = length(def.dimensions) @@ -171,7 +177,8 @@ function check_parameter_dimensions(md::ModelDef, value::AbstractArray, dims::Ve end end -function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) +# TBD: is this needed for composites? +function datum_size(md::ModelDef, comp_def::LeafComponentDef, datum_name::Symbol) dims = dimensions(comp_def, datum_name) if dims[1] == :time time_length = getspan(md, comp_def)[1] @@ -271,22 +278,18 @@ function isuniform(values) end end -#needed when time dimension is defined using a single integer +# needed when time dimension is defined using a single integer function isuniform(values::Int) return true end -# function isuniform(values::AbstractRange{Int}) -# return isuniform(collect(values)) -# end - # # Parameters # external_params(md::ModelDef) = md.external_params -function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) +function addparameter(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit, default) p = DatumDef(name, datatype, dimensions, description, unit, :parameter, default) comp_def.parameters[name] = p return p @@ -297,14 +300,22 @@ function addparameter(comp_id::ComponentId, name, datatype, dimensions, descript end """ - parameters(comp_def::ComponentDef) + parameters(comp_def::LeafComponentDef) Return a list of the parameter definitions for `comp_def`. """ -parameters(comp_def::ComponentDef) = values(comp_def.parameters) +parameters(comp_def::LeafComponentDef) = values(comp_def.parameters) + +function parameters(ccd::CompositeComponentDef) + params = Vector{DatumDef}() + for cd in ccd.comps + append!(params, collect(parameters(cd))) + end +end + """ - parameters(comp_id::ComponentDef) + parameters(comp_id::ComponentId) Return a list of the parameter definitions for `comp_id`. """ @@ -317,11 +328,11 @@ Return a list of all parameter names for a given component `comp_name` in a mode """ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) -parameter_names(comp_def::ComponentDef) = [name(param) for param in parameters(comp_def)] +parameter_names(comp_def::AbstractComponentDef) = [name(param) for param in parameters(comp_def)] parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) -function parameter(comp_def::ComponentDef, name::Symbol) +function parameter(comp_def::LeafComponentDef, name::Symbol) try return comp_def.parameters[name] catch @@ -329,6 +340,15 @@ function parameter(comp_def::ComponentDef, name::Symbol) end end +# TBD: use ccd.external_params or ccd.exports? +function parameter(ccd::CompositeComponentDef, name::Symbol) + try + return ccd.parameters[name] + catch + error("Parameter $name was not found in component $(ccd.name)") + end +end + function parameter_unit(md::ModelDef, comp_name::Symbol, param_name::Symbol) param = parameter(md, comp_name, param_name) return param.unit @@ -409,11 +429,18 @@ end # # Variables # -variables(comp_def::ComponentDef) = values(comp_def.variables) +variables(comp_def::LeafComponentDef) = values(comp_def.variables) + +function variables(ccd::CompositeComponentDef) + vars = Vector{DatumDef}() + for cd in ccd.comps + append!(vars, collect(variables(cd))) + end +end variables(comp_id::ComponentId) = variables(compdef(comp_id)) -function variable(comp_def::ComponentDef, var_name::Symbol) +function variable(comp_def::LeafComponentDef, var_name::Symbol) try return comp_def.variables[var_name] catch @@ -432,7 +459,7 @@ Return a list of all variable names for a given component `comp_name` in a model """ variable_names(md::ModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) -variable_names(comp_def::ComponentDef) = [name(var) for var in variables(comp_def)] +variable_names(comp_def::AbstractComponentDef) = [name(var) for var in variables(comp_def)] function variable_unit(md::ModelDef, comp_name::Symbol, var_name::Symbol) @@ -445,14 +472,14 @@ function variable_dimensions(md::ModelDef, comp_name::Symbol, var_name::Symbol) return var.dimensions end -# Add a variable to a ComponentDef -function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) +# Add a variable to a LeafComponentDef +function addvariable(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit) v = DatumDef(name, datatype, dimensions, description, unit, :variable) comp_def.variables[name] = v return v end -# Add a variable to a ComponentDef referenced by ComponentId +# Add a variable to a LeafComponentDef referenced by ComponentId function addvariable(comp_id::ComponentId, name, datatype, dimensions, description, unit) addvariable(compdef(comp_id), name, datatype, dimensions, description, unit) end @@ -467,7 +494,7 @@ function getspan(md::ModelDef, comp_name::Symbol) return getspan(md, comp_def) end -function getspan(md::ModelDef, comp_def::ComponentDef) +function getspan(md::ModelDef, comp_def::AbstractComponentDef) first = first_period(md, comp_def) last = last_period(md, comp_def) times = time_labels(md) @@ -476,7 +503,7 @@ function getspan(md::ModelDef, comp_def::ComponentDef) return size(times[first_index:last_index]) end -function set_run_period!(comp_def::ComponentDef, first, last) +function set_run_period!(comp_def::LeafComponentDef, first, last) comp_def.first = first comp_def.last = last return nothing @@ -489,18 +516,18 @@ const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} """ - add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) + add_comp!(md::ModelDef, comp_def::LeafComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the model indcated by `md`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; +function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) # check that a time dimension has been set - if !haskey(dimensions(md), :time) + if ! haskey(dimensions(md), :time) error("Cannot add component to model without first setting time dimension.") end @@ -535,7 +562,7 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; if before === nothing && after === nothing md.comp_defs[comp_name] = comp_def # just add it to the end else - new_comps = OrderedDict{Symbol, ComponentDef}() + new_comps = OrderedDict{Symbol, AbstractComponentDef}() if before !== nothing if ! hascomp(md, before) @@ -693,14 +720,14 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end """ - copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) + copy_comp_def(comp_def::AbstractComponentDef, comp_name::Symbol) Create a mostly-shallow copy of `comp_def` (named `comp_name`), but make a deep copy of its ComponentId so we can rename the copy without affecting the original. """ -function copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) +function copy_comp_def(comp_def::LeafComponentDef, comp_name::Symbol) comp_id = comp_def.comp_id - obj = ComponentDef(comp_id) + obj = LeafComponentDef(comp_id) # Use the comp_id as is, since this identifies the run_timestep function, but # use an alternate name to reference it in the model's component list. @@ -715,6 +742,34 @@ function copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) return obj end +function copy_comp_def(ccd::CompositeComponentDef, comp_name::Symbol) + comp_id = ccd.comp_id + obj = CompositeComponentDef(comp_id) + obj.name = comp_name + + append!(obj.comps, [copy_comp_def(cd) for cd in ccd.comps]) + + append!(obj.bindings, ccd.bindings) # TBD: need to copy these deeply? + append!(obj.exports, ccd.exports) # TBD: ditto? + + # TBD: what to do with these? + # internal_param_conns::Vector{InternalParameterConnection} + # external_param_conns::Vector{ExternalParameterConnection} + + # Names of external params that the ConnectorComps will use as their :input2 parameters. + append!(obj.backups, ccd.backups) + + external_params::Dict{Symbol, ModelParameter} + + if ccd.sorted_comps === nothing + obj.sorted_comps = nothing + else + append!(obj.sorted_comps, ccd.sorted_comps) + end + + return obj +end + """ copy_external_params(md::ModelDef) diff --git a/src/core/model.jl b/src/core/model.jl index d58106c3e..dc7996c99 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -4,16 +4,6 @@ # using MacroTools -# Simplify delegation of calls to ::Model to internal ModelInstance or ModelDelegate objects. -macro modelegate(ex) - if @capture(ex, fname_(varname_::Model, args__) => rhs_) - result = esc(:($fname($varname::Model, $(args...)) = $fname($varname.$rhs, $(args...)))) - #println(result) - return result - end - error("Calls to @modelegate must be of the form 'func(m::Model, args...) => X', where X is either mi or md'. Expression was: $ex") -end - """ modeldef(m) @@ -23,23 +13,23 @@ modeldef(m::Model) = m.md modelinstance(m::Model) = m.mi -@modelegate compinstance(m::Model, name::Symbol) => mi +@delegate compinstance(m::Model, name::Symbol) => mi -@modelegate number_type(m::Model) => md +@delegate number_type(m::Model) => md -@modelegate external_param_conns(m::Model) => md +@delegate external_param_conns(m::Model) => md -@modelegate internal_param_conns(m::Model) => md +@delegate internal_param_conns(m::Model) => md -@modelegate external_params(m::Model) => md +@delegate external_params(m::Model) => md -@modelegate external_param(m::Model, name::Symbol) => md +@delegate external_param(m::Model, name::Symbol) => md -@modelegate connected_params(m::Model, comp_name::Symbol) => md +@delegate connected_params(m::Model, comp_name::Symbol) => md -@modelegate unconnected_params(m::Model) => md +@delegate unconnected_params(m::Model) => md -@modelegate add_connector_comps(m::Model) => md +@delegate add_connector_comps(m::Model) => md # Forget any previously built model instance (i.e., after changing the model def). # This should be called by all functions that modify the Model's underlying ModelDef. @@ -182,27 +172,27 @@ end Return an iterator on the components in model `m`. """ -@modelegate components(m::Model) => mi +@delegate components(m::Model) => mi -@modelegate compdefs(m::Model) => md +@delegate compdefs(m::Model) => md -@modelegate compdef(m::Model, comp_name::Symbol) => md +@delegate compdef(m::Model, comp_name::Symbol) => md -@modelegate numcomponents(m::Model) => md +@delegate numcomponents(m::Model) => md -@modelegate first_and_step(m::Model) => md +@delegate first_and_step(m::Model) => md -@modelegate time_labels(m::Model) => md +@delegate time_labels(m::Model) => md # Return the number of timesteps a given component in a model will run for. -@modelegate getspan(m::Model, comp_name::Symbol) => md +@delegate getspan(m::Model, comp_name::Symbol) => md """ - datumdef(comp_def::ComponentDef, item::Symbol) + datumdef(comp_def::LeafComponentDef, item::Symbol) Return a DatumDef for `item` in the given component `comp_def`. """ -function datumdef(comp_def::ComponentDef, item::Symbol) +function datumdef(comp_def::LeafComponentDef, item::Symbol) if haskey(comp_def.variables, item) return comp_def.variables[item] @@ -213,6 +203,10 @@ function datumdef(comp_def::ComponentDef, item::Symbol) end end +# TBD: what to do here? Do we expose non-exported data? +function datumdef(comp_def::CompositeComponentDef, item::Symbol) +end + datumdef(m::Model, comp_name::Symbol, item::Symbol) = datumdef(compdef(m.md, comp_name), item) """ @@ -223,10 +217,10 @@ in the given component `comp_name` in model `m`. """ dimensions(m::Model, comp_name::Symbol, datum_name::Symbol) = dimensions(compdef(m, comp_name), datum_name) -@modelegate dimension(m::Model, dim_name::Symbol) => md +@delegate dimension(m::Model, dim_name::Symbol) => md # Allow access of the form my_model[:grosseconomy, :tfp] -@modelegate Base.getindex(m::Model, comp_name::Symbol, datum_name::Symbol) => mi +@delegate Base.getindex(m::Model, comp_name::Symbol, datum_name::Symbol) => mi """ set_dimension!(m::Model, name::Symbol, keys::Union{Vector, Tuple, AbstractRange}) @@ -239,15 +233,15 @@ function set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, decache(m) end -@modelegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md +@delegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md -@modelegate parameter_names(m::Model, comp_name::Symbol) => md +@delegate parameter_names(m::Model, comp_name::Symbol) => md -@modelegate parameter_dimensions(m::Model, comp_name::Symbol, param_name::Symbol) => md +@delegate parameter_dimensions(m::Model, comp_name::Symbol, param_name::Symbol) => md -@modelegate parameter_unit(m::Model, comp_name::Symbol, param_name::Symbol) => md +@delegate parameter_unit(m::Model, comp_name::Symbol, param_name::Symbol) => md -parameter(m::Model, comp_def::ComponentDef, param_name::Symbol) = parameter(comp_def, param_name) +parameter(m::Model, comp_def::AbstractComponentDef, param_name::Symbol) = parameter(comp_def, param_name) parameter(m::Model, comp_name::Symbol, param_name::Symbol) = parameter(m, compdef(m, comp_name), param_name) @@ -280,7 +274,7 @@ Return a list of the variable definitions for `comp_name` in model `m`. """ variables(m::Model, comp_name::Symbol) = variables(compdef(m, comp_name)) -@modelegate variable_names(m::Model, comp_name::Symbol) => md +@delegate variable_names(m::Model, comp_name::Symbol) => md """ set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) diff --git a/src/core/types.jl b/src/core/types.jl index ac690c95e..5b3540da9 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,3 +1,32 @@ +# +# 0. Macro used in several places, so centralized here even though not type-related +# +using MacroTools + +# Convert a list of args with optional type specs to just the arg symbols +_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] + +""" +Macro to define a method that simply delegate to a method with the same signature +but using the specified field name of the original first argument as the first arg +in the delegated call. That is, + + `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` + +expands to: + + `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` +""" +macro delegate(ex) + if @capture(ex, fname_(varname_::T_, args__) => rhs_) + argnames = _arg_names(args) + result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) + return result + end + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") +end + + # # 1. Types supporting parameterized Timestep and Clock objects # @@ -168,6 +197,8 @@ struct ComponentId comp_name::Symbol end +ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) + # Indicates that the object has a `name` attribute abstract type NamedDef end @@ -204,21 +235,29 @@ mutable struct DimensionDef <: NamedDef name::Symbol end -mutable struct ComponentDef <: NamedDef - name::Symbol +# Stores references to the name of a component variable or parameter +struct DatumReference comp_id::ComponentId + datum_name::Symbol +end + +# Supertype of LeafComponentDef and LeafComponentInstance +abstract type AbstractComponentDef <: NamedDef end + +mutable struct LeafComponentDef <: AbstractComponentDef + comp_id::ComponentId + name::Symbol variables::OrderedDict{Symbol, DatumDef} parameters::OrderedDict{Symbol, DatumDef} dimensions::OrderedDict{Symbol, DimensionDef} first::Union{Nothing, Int} last::Union{Nothing, Int} - # ComponentDefs are created "empty"; elements are subsequently added - # to them via addvariable, add_dimension!, etc. - function ComponentDef(comp_id::ComponentId) + # LeafComponentDefs are created "empty". Elements are subsequently added. + function LeafComponentDef(comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name) self = new() - self.name = comp_id.comp_name self.comp_id = comp_id + self.name = comp_name self.variables = OrderedDict{Symbol, DatumDef}() self.parameters = OrderedDict{Symbol, DatumDef}() self.dimensions = OrderedDict{Symbol, DimensionDef}() @@ -227,17 +266,21 @@ mutable struct ComponentDef <: NamedDef end end -# Declarative definition of a model, used to create a ModelInstance -mutable struct ModelDef - module_name::Symbol # the module in which this model was defined - - # Components keyed by symbolic name, allowing a given component - # to occur multiple times within a model. - comp_defs::OrderedDict{Symbol, ComponentDef} +# *Def implementation doesn't need to be performance-optimized since these +# are used only to create *Instance objects that are used at run-time. With +# this in mind, we don't create dictionaries of vars, params, or dims in the +# CompositeComponentDef since this would complicate matters if a user decides +# to add/modify/remove a component. Instead of maintaining a secondary dict, +# we just iterate over sub-components at run-time as needed. - dimensions::Dict{Symbol, Dimension} +global const BindingTypes = Union{Int, Float64, DatumReference} - number_type::DataType +mutable struct CompositeComponentDef <: AbstractComponentDef + comp_id::ComponentId + name::Symbol + comps::Vector{AbstractComponentDef} + bindings::Vector{Pair{DatumReference, BindingTypes}} + exports::Vector{Pair{DatumReference, Symbol}} internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} @@ -249,21 +292,40 @@ mutable struct ModelDef sorted_comps::Union{Nothing, Vector{Symbol}} + function CompositeComponentDef(comp_id::ComponentId, name::Symbol, + comps::Vector{AbstractComponentDef}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) + internal_param_conns = Vector{InternalParameterConnection}() + external_param_conns = Vector{ExternalParameterConnection}() + external_params = Dict{Symbol, ModelParameter}() + backups = Vector{Symbol}() + sorted_comps = nothing + is_uniform = true + + return new(comp_id, name, comps, bindings, exports, + internal_param_conns, external_param_conns, + backups, external_params, sorted_comps) + end + + function CompositeComponentDef(comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name) + comps = Vector{AbstractComponentDef}() + bindings = Vector{Pair{DatumReference, BindingTypes}}() + exports = Vector{Pair{DatumReference, Symbol}}() + return new(comp_id, comp_name, comps, bindings, exports) + end +end + +mutable struct ModelDef + ccd::CompositeComponentDef + dimensions::Dict{Symbol, Dimension} + number_type::DataType is_uniform::Bool - function ModelDef(number_type=Float64) - self = new() - self.module_name = nameof(@__MODULE__) # TBD: fix this; should by module model is defined in - self.comp_defs = OrderedDict{Symbol, ComponentDef}() - self.dimensions = Dict{Symbol, Dimension}() - self.number_type = number_type - self.internal_param_conns = Vector{InternalParameterConnection}() - self.external_param_conns = Vector{ExternalParameterConnection}() - self.external_params = Dict{Symbol, ModelParameter}() - self.backups = Vector{Symbol}() - self.sorted_comps = nothing - self.is_uniform = true - return self + function ModelDef(ccd::CompositeComponentDef, number_type::DataType=Float64) + dimensions = Dict{Symbol, Dimension}() + is_uniform = true + return new(ccd, dimensions, number_type, is_uniform) end end @@ -271,6 +333,78 @@ end # 5. Types supporting instantiated models and their components # +abstract type AbstractComponentInstance end + +mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + comp_name::Symbol + comp_id::ComponentId + variables::TV + parameters::TP + dim_dict::Dict{Symbol, Vector{Int}} + first::Int + last::Int + init::Union{Nothing, Function} + run_timestep::Union{Nothing, Function} + + function LeafComponentInstance{TV, TP}(comp_def::LeafComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def); + is_composite::Bool=false) where {TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} + self = new{TV, TP}() + self.comp_id = comp_id = comp_def.comp_id + self.comp_name = name + self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.variables = vars + self.parameters = pars + self.first = comp_def.first + self.last = comp_def.last + + comp_module = Base.eval(Main, comp_id.module_name) + + # The try/catch allows components with no run_timestep function (as in some of our test cases) + # All CompositeComponentInstances use a standard method that just loops over inner components. + # TBD: use FunctionWrapper here? + function get_func(name) + func_name = Symbol("$(name)_$(comp_name)") + try + Base.eval(comp_module, func_name) + catch err + nothing + end + end + + # TBD: is `is_composite` necessary? + self.run_timestep = is_composite ? nothing : get_func("run_timestep") + self.init = is_composite ? nothing : get_func("init") + + return self + end +end + +mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance + + # TV, TP, and dim_dict are computed by aggregating all the vars and params from the CompositeComponent's + # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the + # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. + leaf::LeafComponentInstance{TV, TP} + comps::Vector{AbstractComponentInstance} + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} + clocks::Union{Nothing, Vector{Clock}} + + function CompositeComponentInstance{TV, TP}( + comp_def::CompositeComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name, true) + comps = Vector{AbstractComponentInstance}() + firsts = Vector{Int}() + lasts = Vector{Int}() + clocks = nothing + return new{TV, TP}(leaf, comps, firsts, lasts, clocks) + end +end + # Supertype for variables and parameters in component instances abstract type ComponentInstanceData end @@ -327,64 +461,10 @@ Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters -# An instance of this type is passed to the run_timestep function of a -# component, typically as the `p` argument. The main role of this type -# is to provide the convenient `p.nameofparameter` syntax. -mutable struct ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - comp_name::Symbol - comp_id::ComponentId - variables::TV - parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} - - first::Int - last::Int - - init::Union{Nothing, Function} - run_timestep::Union{Nothing, Function} - - function ComponentInstance{TV, TP}(comp_def::ComponentDef, - vars::TV, pars::TP, - first::Int, last::Int, - name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} - self = new{TV, TP}() - - self.comp_id = comp_id = comp_def.comp_id - self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage - - self.variables = vars - self.parameters = pars - self.first = first - self.last = last - - comp_name = comp_id.comp_name - module_name = comp_id.module_name - comp_module = Base.eval(Main, module_name) - - # TBD: use FunctionWrapper here? - function get_func(name) - func_name = Symbol("$(name)_$(comp_name)") - try - Base.eval(comp_module, func_name) - catch err - # No need to warn about this... - # @warn "Failed to evaluate function name $func_name in module $comp_module" - nothing - end - end - - self.init = get_func("init") - self.run_timestep = get_func("run_timestep") - - return self - end -end - # This type holds the values of a built model and can actually be run. mutable struct ModelInstance md::ModelDef + cci::Union{Nothing, CompositeComponentInstance} # Ordered list of components (including hidden ConnectorComps) components::OrderedDict{Symbol, ComponentInstance} @@ -405,7 +485,6 @@ end # # 6. User-facing Model types providing a simplified API to model definitions and instances. # - """ Model @@ -416,8 +495,11 @@ a `number_type` of `Float64`. """ mutable struct Model md::ModelDef - mi::Union{Nothing, ModelInstance} + # TBD: need only one of these... + mi::Union{Nothing, ModelInstance} + cci::Union{Nothing, CompositeComponentInstance} + function Model(number_type::DataType=Float64) return new(ModelDef(number_type), nothing) end diff --git a/test/test_composite.jl b/test/test_composite.jl new file mode 100644 index 000000000..0541288b5 --- /dev/null +++ b/test/test_composite.jl @@ -0,0 +1,62 @@ +module TestComposite + +using Test +using Mimi + +import Mimi: + reset_compdefs, compdef, ComponentId, DatumRef, CompositeComponentDef, NewModel + +reset_compdefs() + + +@defcomp Comp1 begin + par1 = Parameter(index=[time]) # external input + var1 = Variable(index=[time]) # computed + + function run_timestep(p, v, d, t) + v.var1[t] = p.par1[t] + end +end + +@defcomp Comp2 begin + par1 = Parameter(index=[time]) # connected to Comp1.var1 + par2 = Parameter(index=[time]) # external input + var1 = Variable(index=[time]) # computed + + function run_timestep(p, v, d, t) + v.var1[t] = p.par1[t] + p.par2[t] + end +end + +@defcomp Comp3 begin + par1 = Parameter(index=[time]) # connected to Comp2.var1 + var1 = Variable(index=[time]) # external output + + function run_timestep(p, v, d, t) + v.var1[t] = p.par1[t] * 2 + end +end + +# Test the calls the macro will produce +let calling_module = @__MODULE__ + global MyComposite + + ccname = :testcomp + ccid = ComponentId(calling_module, ccname) + comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] + + bindings = [DatumRef(Comp2, :par1) => 5, # bind Comp1.par1 to constant value of 5 + DatumRef(Comp2, :par1) => DatumRef(Comp1, :var1), # connect target Comp2.par1 to source Comp1.var1 + DatumRef(Comp3, :par1) => DatumRef(Comp2, :var1)] + + exports = [DatumRef(Comp1, :par1) => :c1p1, # i.e., export Comp1.par1 as :c1p1 + DatumRef(Comp2, :par2) => :c2p2, + DatumRef(Comp3, :var1) => :c3v1] + + MyComposite = NewModel(CompositeComponentDef(ccid, ccname, comps, bindings, exports)) + nothing +end + +end # module + +nothing \ No newline at end of file diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index f24b9b6b5..80115e78c 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -41,7 +41,7 @@ citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md @test typeof(ci) <: Mimi.ComponentInstance && ci == mi.components[:testcomp1] -@test typeof(cdef) <: Mimi.ComponentDef && cdef == compdef(ci.comp_id) +@test typeof(cdef) <: Mimi.LeafComponentDef && cdef == compdef(ci.comp_id) @test name(ci) == :testcomp1 @test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) == Mimi.ComponentInstance From 8a012cc0a255e0ae6fcfe34e2e01451a491c87cc Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 24 Oct 2018 09:51:44 -0700 Subject: [PATCH 10/81] WIP - Composite component checkpoint. --- src/core/{composite.jl => defcomposite.jl} | 63 ---------- src/core/defs.jl | 21 +++- src/core/instances.jl | 138 ++++++++++++++------- src/core/model.jl | 5 +- src/core/types.jl | 30 ++--- 5 files changed, 121 insertions(+), 136 deletions(-) rename src/core/{composite.jl => defcomposite.jl} (76%) diff --git a/src/core/composite.jl b/src/core/defcomposite.jl similarity index 76% rename from src/core/composite.jl rename to src/core/defcomposite.jl index e903fe82a..9089316fb 100644 --- a/src/core/composite.jl +++ b/src/core/defcomposite.jl @@ -1,68 +1,5 @@ using MacroTools -components(obj::CompositeComponentInstance) = obj.comps - -compid(ci::LeafComponentInstance) = ci.comp_id -name(ci::LeafComponentInstance) = ci.comp_name -dims(ci::LeafComponentInstance) = ci.dim_dict -variables(ci::LeafComponentInstance) = ci.variables -parameters(ci::LeafComponentInstance) = ci.parameters - -# CompositeComponentInstance delegates these to its internal LeafComponentInstance -@delegate compid(ci::CompositeComponentInstance) => leaf -@delegate name(ci::CompositeComponentInstance) => leaf -@delegate dims(ci::CompositeComponentInstance) => leaf -@delegate variables(ci::CompositeComponentInstance) => leaf -@delegate parameters(ci::CompositeComponentInstance) => leaf - -@delegate variables(m::Model) => cci -@delegate parameters(m::Model) => cci -@delegate components(m::Model) => cci -@delegate firsts(m::Model) => cci -@delegate lasts(m::Model) => cci -@delegate clocks(m::Model) => cci - -function reset_variables(cci::CompositeComponentInstance) - for ci in components(cci) - reset_variables(ci) - end - return nothing -end - -function init(ci::LeafComponentInstance) - reset_variables(ci) - - if ci.init != nothing - ci.init(parameters(ci), variables(ci), dims(ci)) - end - return nothing -end - -function init(cci::CompositeComponentInstance) - for ci in components(cci) - init(ci) - end - return nothing -end - -function run_timestep(ci::LeafComponentInstance, clock::Clock) - if ci.run_timestep != nothing - ci.run_timestep(parameters(ci), variables(ci), dims(ci), clock.ts) - end - - # TBD: move this outside this func if components share a clock - advance(clock) - - return nothing -end - -function run_timestep(cci::CompositeComponentInstance, clock::Clock) - for ci in components(cci) - run_timestep(ci, clock) - end - return nothing -end - # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) _arg_name(arg_tup) = arg_tup[1] _arg_type(arg_tup) = arg_tup[2] diff --git a/src/core/defs.jl b/src/core/defs.jl index eb868029e..347879cd3 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -131,13 +131,24 @@ end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -dimensions(comp_def::LeafComponentDef) = values(comp_def.dimensions) +dimensions(cd::LeafComponentDef) = values(cd.dimensions) + +function dimensions(ccd::CompositeComponentDef) + dims = Vector{DimensionDef}() + for cd in components(ccd) + append!(dims, dimensions(cd)) + end + + # use Set to eliminate duplicates + return collect(Set(dims)) +end dimensions(def::DatumDef) = def.dimensions +# TBD: make this work for AbstractComponentDef? dimensions(comp_def::LeafComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) -dim_count(def::DatumDef) = length(def.dimensions) +dim_count(def::DatumDef) = length(dimensions(def)) datatype(def::DatumDef) = def.datatype @@ -340,10 +351,9 @@ function parameter(comp_def::LeafComponentDef, name::Symbol) end end -# TBD: use ccd.external_params or ccd.exports? function parameter(ccd::CompositeComponentDef, name::Symbol) try - return ccd.parameters[name] + return ccd.external_params[name] catch error("Parameter $name was not found in component $(ccd.name)") end @@ -716,7 +726,6 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com # Re-add add_comp!(md, comp_id, comp_name; first=first, last=last, before=before, after=after) - end """ @@ -749,7 +758,7 @@ function copy_comp_def(ccd::CompositeComponentDef, comp_name::Symbol) append!(obj.comps, [copy_comp_def(cd) for cd in ccd.comps]) - append!(obj.bindings, ccd.bindings) # TBD: need to copy these deeply? + append!(obj.bindings, ccd.bindings) # TBD: need to deepcopy these? append!(obj.exports, ccd.exports) # TBD: ditto? # TBD: what to do with these? diff --git a/src/core/instances.jl b/src/core/instances.jl index bb4042719..51aaf89ad 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -9,39 +9,69 @@ Return the `ModelDef` contained by ModelInstance `mi`. """ modeldef(mi::ModelInstance) = mi.md -compinstance(mi::ModelInstance, name::Symbol) = mi.components[name] +compinstance(cci::CompositeComponentInstance, name::Symbol) = cci.comp_dict[name] -compdef(ci::ComponentInstance) = compdef(ci.comp_id) +@delegate compinstance(mi::ModelInstance, name::Symbol) => cci + +compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) + +@delegate compdef(cci::CompositeComponentInstance) => leaf """ name(ci::ComponentInstance) Return the name of the component `ci`. """ -name(ci::ComponentInstance) = ci.comp_name +name(lci::LeafComponentInstance) = lci.comp_name + +compid(ci::LeafComponentInstance) = ci.comp_id +dims(ci::LeafComponentInstance) = ci.dim_dict +variables(ci::LeafComponentInstance) = ci.variables +parameters(ci::LeafComponentInstance) = ci.parameters +first_period(ci::LeafComponentInstance) = ci.first +last_period(ci::LeafComponentInstance) = ci.last + +# CompositeComponentInstance delegates these to its internal LeafComponentInstance +@delegate name(ci::CompositeComponentInstance) => leaf +@delegate compid(ci::CompositeComponentInstance) => leaf +@delegate dims(ci::CompositeComponentInstance) => leaf +@delegate variables(ci::CompositeComponentInstance) => leaf +@delegate parameters(ci::CompositeComponentInstance) => leaf +@delegate first_period(cci:CompositeComponentInstance) => leaf +@delegate last_period(cci:CompositeComponentInstance) => leaf """ components(mi::ModelInstance) -Return an iterator on the components in model instance `mi`. +Return an iterator over components in model instance `mi`. """ -components(mi::ModelInstance) = values(mi.components) +@delegate components(mi::ModelInstance) => cci@delegate variables(m::Model) => cci +@delegate parameters(m::Model) => cci +@delegate firsts(m::Model) => cci +@delegate lasts(m::Model) => cci +@delegate clocks(m::Model) => cci -""" - add_comp!(mi::ModelInstance, ci::ComponentInstance) +components(cci::CompositeComponentInstance) = values(cci.comp_dict) -Add the component `ci` to the `ModelInstance` `mi`'s list of components, and add -the `first` and `last` of `mi` to the ends of the `firsts` and `lasts` lists of -`mi`, respectively. -""" -function add_comp!(mi::ModelInstance, ci::ComponentInstance) - mi.components[name(ci)] = ci +function add_comp!(cci::CompositeComponentInstance, ci::AbstractComponentInstance) + cci.comp_dict[name(ci)] = ci - push!(mi.firsts, ci.first) - push!(mi.lasts, ci.last) + push!(cci.firsts, first_period(ci)) + push!(cci.lasts, last_period(ci)) end +""" + add_comp!(mi::ModelInstance, ci::AbstractComponentInstance) + +Add the (leaf or composite) component `ci` to the `ModelInstance` `mi`'s list of +components, and add the `first` and `last` of `mi` to the ends of the `firsts` and +`lasts` lists of `mi`, respectively. +""" +@delegate add_comp!(mi::ModelInstance, ci::AbstractComponentInstance) => cci + +# # Setting/getting parameter and variable values +# # Get the object stored for the given variable, not the value of the variable. # This is used in the model building process to connect internal parameters. @@ -89,11 +119,11 @@ end end """ - get_param_value(ci::ComponentInstance, name::Symbol) + get_param_value(ci::AbstractComponentInstance, name::Symbol) -Return the value of parameter `name` in component `ci`. +Return the value of parameter `name` in (leaf or composite) component `ci`. """ -function get_param_value(ci::ComponentInstance, name::Symbol) +function get_param_value(ci::LeafComponentInstance, name::Symbol) try return getproperty(ci.parameters, name) catch err @@ -105,12 +135,14 @@ function get_param_value(ci::ComponentInstance, name::Symbol) end end +@delegate get_param_value(ci::CompositeComponentInstance, name::Symbol) => leaf + """ - get_var_value(ci::ComponentInstance, name::Symbol) + get_var_value(ci::AbstractComponentInstance, name::Symbol) Return the value of variable `name` in component `ci`. """ -function get_var_value(ci::ComponentInstance, name::Symbol) +function get_var_value(ci::LeafComponentInstance, name::Symbol) try # println("Getting $name from $(ci.variables)") return getproperty(ci.variables, name) @@ -123,9 +155,15 @@ function get_var_value(ci::ComponentInstance, name::Symbol) end end -set_param_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) +@delegate get_var_value(ci::CompositeComponentInstance, name::Symbol) => leaf + +set_param_value(ci::LeafComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) + +@delegate set_param_value(ci::CompositeComponentInstance, name::Symbol, value) => leaf -set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) +set_var_value(ci::LeafComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) + +@delegate set_var_value(ci::CompositeComponentInstance, name::Symbol, value) => leaf # Allow values to be obtained from either parameter type using one method name. value(param::ScalarModelParameter) = param.value @@ -143,7 +181,7 @@ Return the `ComponentInstanceVariables` for `comp_name` in ModelInstance 'mi'. """ variables(mi::ModelInstance, comp_name::Symbol) = variables(compinstance(mi, comp_name)) -variables(ci::ComponentInstance) = ci.variables +variables(ci::LeafComponentInstance) = ci.variables """ parameters(mi::ModelInstance, comp_name::Symbol) @@ -155,9 +193,9 @@ parameters(mi::ModelInstance, comp_name::Symbol) = parameters(compinstance(mi, c """ parameters(ci::ComponentInstance) -Return an iterable over the parameters in `ci`. +Return an iterator over the parameters in `ci`. """ -parameters(ci::ComponentInstance) = ci.parameters +parameters(ci::LeafComponentInstance) = ci.parameters function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) @@ -207,7 +245,7 @@ function make_clock(mi::ModelInstance, ntimesteps, time_keys::Vector{Int}) end end -function reset_variables(ci::ComponentInstance) +function reset_variables(ci::LeafComponentInstance) # println("reset_variables($(ci.comp_id))") vars = ci.variables @@ -226,36 +264,52 @@ function reset_variables(ci::ComponentInstance) end end -function init(mi::ModelInstance) - for ci in components(mi) - init(ci) +function reset_variables(cci::CompositeComponentInstance) + for ci in components(cci) + reset_variables(ci) end + return nothing end -function init(ci::ComponentInstance) +function init(ci::LeafComponentInstance) reset_variables(ci) - if ci.init !== nothing - ci.init(ci.parameters, ci.variables, DimDict(ci.dim_dict)) + + if ci.init != nothing + ci.init(parameters(ci), variables(ci), dims(ci)) end + return nothing end -function run_timestep(ci::ComponentInstance, clock::Clock) - if ci.run_timestep === nothing - return +function init(cci::CompositeComponentInstance) + for ci in components(cci) + init(ci) end + return nothing +end - pars = ci.parameters - vars = ci.variables - dims = ci.dim_dict - t = clock.ts +@delegate init(mi::ModelInstance) => cci - ci.run_timestep(pars, vars, DimDict(dims), t) +function run_timestep(ci::LeafComponentInstance, clock::Clock) + if ci.run_timestep != nothing + ci.run_timestep(parameters(ci), variables(ci), dims(ci), clock.ts) + end + + # TBD: move this outside this func if components share a clock advance(clock) - nothing + + return nothing +end + +function run_timestep(cci::CompositeComponentInstance, clock::Clock) + for ci in components(cci) + run_timestep(ci, clock) + end + return nothing end function _run_components(mi::ModelInstance, clock::Clock, - firsts::Vector{Int}, lasts::Vector{Int}, comp_clocks::Vector{Clock{T}}) where {T <: AbstractTimestep} + firsts::Vector{Int}, lasts::Vector{Int}, + comp_clocks::Vector{Clock{T}}) where {T <: AbstractTimestep} comp_instances = components(mi) tups = collect(zip(comp_instances, firsts, lasts, comp_clocks)) diff --git a/src/core/model.jl b/src/core/model.jl index dc7996c99..9ea4f27b8 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -203,9 +203,8 @@ function datumdef(comp_def::LeafComponentDef, item::Symbol) end end -# TBD: what to do here? Do we expose non-exported data? -function datumdef(comp_def::CompositeComponentDef, item::Symbol) -end +# TBD: what to do here? Do we expose non-exported data? Have an internal LeafComponentDef that stores this stuff? +@delegate datumdef(comp_def::CompositeComponentDef, item::Symbol) => leaf datumdef(m::Model, comp_name::Symbol, item::Symbol) = datumdef(compdef(m.md, comp_name), item) diff --git a/src/core/types.jl b/src/core/types.jl index 5b3540da9..70f38935c 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -301,7 +301,6 @@ mutable struct CompositeComponentDef <: AbstractComponentDef external_params = Dict{Symbol, ModelParameter}() backups = Vector{Symbol}() sorted_comps = nothing - is_uniform = true return new(comp_id, name, comps, bindings, exports, internal_param_conns, external_param_conns, @@ -373,7 +372,8 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com end end - # TBD: is `is_composite` necessary? + # `is_composite` indicates a LeafComponentInstance used to store summary + # data for CompositeComponentInstance and is not itself runnable. self.run_timestep = is_composite ? nothing : get_func("run_timestep") self.init = is_composite ? nothing : get_func("init") @@ -387,7 +387,7 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. leaf::LeafComponentInstance{TV, TP} - comps::Vector{AbstractComponentInstance} + comp_dict::OrderedDict{Symbol, AbstractComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Union{Nothing, Vector{Clock}} @@ -397,11 +397,11 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name, true) - comps = Vector{AbstractComponentInstance}() + comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() firsts = Vector{Int}() lasts = Vector{Int}() clocks = nothing - return new{TV, TP}(leaf, comps, firsts, lasts, clocks) + return new{TV, TP}(leaf, comp_dict, firsts, lasts, clocks) end end @@ -461,24 +461,13 @@ Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters -# This type holds the values of a built model and can actually be run. +# ModelInstance holds the built model that is ready to be run mutable struct ModelInstance md::ModelDef cci::Union{Nothing, CompositeComponentInstance} - # Ordered list of components (including hidden ConnectorComps) - components::OrderedDict{Symbol, ComponentInstance} - - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - function ModelInstance(md::ModelDef) - self = new() - self.md = md - self.components = OrderedDict{Symbol, ComponentInstance}() - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - return self + return new(md, nothing) end end @@ -495,10 +484,7 @@ a `number_type` of `Float64`. """ mutable struct Model md::ModelDef - - # TBD: need only one of these... - mi::Union{Nothing, ModelInstance} - cci::Union{Nothing, CompositeComponentInstance} + mi::Union{Nothing, ModelInstance} function Model(number_type::DataType=Float64) return new(ModelDef(number_type), nothing) From f204ff58f388d64f0f4e4f0146bca512e23b9662 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 24 Oct 2018 21:36:16 -0700 Subject: [PATCH 11/81] WIP - Composite Component checkpoint --- src/Mimi.jl | 2 +- src/core/instances.jl | 31 ++++----- src/core/types.jl | 139 ++++++++++++++++++++++------------------- src/utils/graph.jl | 16 +++-- test/test_composite.jl | 27 +++++--- 5 files changed, 122 insertions(+), 93 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index f51daf839..970d31b98 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -42,13 +42,13 @@ export variables include("core/types.jl") -include("core/composite.jl") # After loading types and delegation macro, the rest can just be alphabetical include("core/build.jl") include("core/connections.jl") include("core/defs.jl") include("core/defcomp.jl") +include("core/defcomposite.jl") include("core/dimensions.jl") include("core/instances.jl") include("core/references.jl") diff --git a/src/core/instances.jl b/src/core/instances.jl index 51aaf89ad..bdb3f78e6 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -10,11 +10,9 @@ Return the `ModelDef` contained by ModelInstance `mi`. modeldef(mi::ModelInstance) = mi.md compinstance(cci::CompositeComponentInstance, name::Symbol) = cci.comp_dict[name] - @delegate compinstance(mi::ModelInstance, name::Symbol) => cci compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) - @delegate compdef(cci::CompositeComponentInstance) => leaf """ @@ -23,30 +21,27 @@ compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) Return the name of the component `ci`. """ name(lci::LeafComponentInstance) = lci.comp_name +@delegate name(ci::CompositeComponentInstance) => leaf compid(ci::LeafComponentInstance) = ci.comp_id +@delegate compid(ci::CompositeComponentInstance) => leaf + dims(ci::LeafComponentInstance) = ci.dim_dict -variables(ci::LeafComponentInstance) = ci.variables -parameters(ci::LeafComponentInstance) = ci.parameters +@delegate dims(ci::CompositeComponentInstance) => leaf + first_period(ci::LeafComponentInstance) = ci.first -last_period(ci::LeafComponentInstance) = ci.last +@delegate first_period(ci::CompositeComponentInstance) => leaf -# CompositeComponentInstance delegates these to its internal LeafComponentInstance -@delegate name(ci::CompositeComponentInstance) => leaf -@delegate compid(ci::CompositeComponentInstance) => leaf -@delegate dims(ci::CompositeComponentInstance) => leaf -@delegate variables(ci::CompositeComponentInstance) => leaf -@delegate parameters(ci::CompositeComponentInstance) => leaf -@delegate first_period(cci:CompositeComponentInstance) => leaf -@delegate last_period(cci:CompositeComponentInstance) => leaf +last_period(ci::LeafComponentInstance) = ci.last +@delegate last_period(ci::CompositeComponentInstance) => leaf """ components(mi::ModelInstance) Return an iterator over components in model instance `mi`. """ -@delegate components(mi::ModelInstance) => cci@delegate variables(m::Model) => cci -@delegate parameters(m::Model) => cci +@delegate components(mi::ModelInstance) => cci + @delegate firsts(m::Model) => cci @delegate lasts(m::Model) => cci @delegate clocks(m::Model) => cci @@ -183,6 +178,9 @@ variables(mi::ModelInstance, comp_name::Symbol) = variables(compinstance(mi, com variables(ci::LeafComponentInstance) = ci.variables +@delegate variables(m::Model) => cci +@delegate variables(ci::CompositeComponentInstance) => leaf + """ parameters(mi::ModelInstance, comp_name::Symbol) @@ -197,6 +195,9 @@ Return an iterator over the parameters in `ci`. """ parameters(ci::LeafComponentInstance) = ci.parameters +@delegate parameters(m::Model) => cci +@delegate parameters(ci::CompositeComponentInstance) => leaf + function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) if !(comp_name in keys(mi.components)) diff --git a/src/core/types.jl b/src/core/types.jl index 70f38935c..d146502e8 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -276,9 +276,9 @@ end global const BindingTypes = Union{Int, Float64, DatumReference} mutable struct CompositeComponentDef <: AbstractComponentDef - comp_id::ComponentId - name::Symbol - comps::Vector{AbstractComponentDef} + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level CompositeComponentDefs (must be referenced by a ModelDef) + name::Union{Nothing, Symbol} + comps::Vector{<:AbstractComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} exports::Vector{Pair{DatumReference, Symbol}} @@ -293,7 +293,7 @@ mutable struct CompositeComponentDef <: AbstractComponentDef sorted_comps::Union{Nothing, Vector{Symbol}} function CompositeComponentDef(comp_id::ComponentId, name::Symbol, - comps::Vector{AbstractComponentDef}, + comps::Vector{<:AbstractComponentDef}, bindings::Vector{Pair{DatumReference, BindingTypes}}, exports::Vector{Pair{DatumReference, Symbol}}) internal_param_conns = Vector{InternalParameterConnection}() @@ -307,12 +307,18 @@ mutable struct CompositeComponentDef <: AbstractComponentDef backups, external_params, sorted_comps) end - function CompositeComponentDef(comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name) + function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}, + comp_name::Union{Nothing, Symbol}=comp_id.comp_name) comps = Vector{AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() return new(comp_id, comp_name, comps, bindings, exports) - end + end + + function CompositeComponentDef() + # Create an anonymous CompositeComponentDef that must be referenced by a ModelDef + return CompositeComponentDef(nothing, nothing) + end end mutable struct ModelDef @@ -326,12 +332,73 @@ mutable struct ModelDef is_uniform = true return new(ccd, dimensions, number_type, is_uniform) end + + function ModelDef(number_type::DataType=Float64) + ccd = CompositeComponentDef() # anonymous top-level CompositeComponentDef + return ModelDef(ccd, number_type) + end end # # 5. Types supporting instantiated models and their components # +# Supertype for variables and parameters in component instances +abstract type ComponentInstanceData end + +struct ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData + nt::NT + + function ComponentInstanceParameters{NT}(nt::NT) where {NT <: NamedTuple} + return new{NT}(nt) + end +end + +function ComponentInstanceParameters(names, types, values) + NT = NamedTuple{names, types} + ComponentInstanceParameters{NT}(NT(values)) +end + +function ComponentInstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + ComponentInstanceParameters{NT}(NT(values)) +end + +struct ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData + nt::NT + + function ComponentInstanceVariables{NT}(nt::NT) where {NT <: NamedTuple} + return new{NT}(nt) + end +end + +function ComponentInstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + ComponentInstanceVariables{NT}(NT(values)) +end + +function ComponentInstanceVariables(names, types, values) + NT = NamedTuple{names, types} + ComponentInstanceVariables{NT}(NT(values)) +end + +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimDict + dict::Dict{Symbol, Vector{Int}} +end + +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimDict of dimensions by name +# as the "d" parameter. +@inline function Base.getproperty(dimdict::DimDict, property::Symbol) + return getfield(dimdict, :dict)[property] +end + +# TBD: try with out where clause, i.e., just obj::ComponentInstanceData +nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) +Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) +Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) +types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters + abstract type AbstractComponentInstance end mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance @@ -387,7 +454,7 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. leaf::LeafComponentInstance{TV, TP} - comp_dict::OrderedDict{Symbol, AbstractComponentInstance} + comp_dict::OrderedDict{Symbol, <: AbstractComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Union{Nothing, Vector{Clock}} @@ -397,7 +464,7 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name, true) - comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() + comps_dict = OrderedDict{Symbol, <: AbstractComponentInstance}() firsts = Vector{Int}() lasts = Vector{Int}() clocks = nothing @@ -405,62 +472,6 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < end end -# Supertype for variables and parameters in component instances -abstract type ComponentInstanceData end - -struct ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData - nt::NT - - function ComponentInstanceParameters{NT}(nt::NT) where {NT <: NamedTuple} - return new{NT}(nt) - end -end - -function ComponentInstanceParameters(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceParameters{NT}(NT(values)) -end - -function ComponentInstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceParameters{NT}(NT(values)) -end - -struct ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData - nt::NT - - function ComponentInstanceVariables{NT}(nt::NT) where {NT <: NamedTuple} - return new{NT}(nt) - end -end - -function ComponentInstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceVariables{NT}(NT(values)) -end - -function ComponentInstanceVariables(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceVariables{NT}(NT(values)) -end - -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimDict - dict::Dict{Symbol, Vector{Int}} -end - -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimDict of dimensions by name -# as the "d" parameter. -@inline function Base.getproperty(dimdict::DimDict, property::Symbol) - return getfield(dimdict, :dict)[property] -end - -# TBD: try with out where clause, i.e., just obj::ComponentInstanceData -nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) -Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) -Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) -types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters - # ModelInstance holds the built model that is ready to be run mutable struct ModelInstance md::ModelDef diff --git a/src/utils/graph.jl b/src/utils/graph.jl index d8ec463b0..f68021819 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -33,10 +33,6 @@ function show(io::IO, m::Model) end end -function get_connections(m::Model, ci::ComponentInstance, which::Symbol) - return get_connections(m, name(ci.comp), which) -end - function _filter_connections(conns::Vector{InternalParameterConnection}, comp_name::Symbol, which::Symbol) if which == :all f = obj -> (obj.src_comp_name == comp_name || obj.dst_comp_name == comp_name) @@ -51,6 +47,18 @@ function _filter_connections(conns::Vector{InternalParameterConnection}, comp_na return collect(Iterators.filter(f, conns)) end +function get_connections(m::Model, ci::LeafComponentInstance, which::Symbol) + return get_connections(m, name(ci.comp), which) +end + +function get_connections(m::Model, cci::CompositeComponentInstance, which::Symbol) + conns = [] + for ci in components(cci) + append!(conns, get_connections(m, ci, which)) + end + return conns +end + function get_connections(m::Model, comp_name::Symbol, which::Symbol) return _filter_connections(internal_param_conns(m.md), comp_name, which) end diff --git a/test/test_composite.jl b/test/test_composite.jl index 0541288b5..d8569d3d5 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - reset_compdefs, compdef, ComponentId, DatumRef, CompositeComponentDef, NewModel + reset_compdefs, compdef, ComponentId, DatumReference, CompositeComponentDef, AbstractComponentDef, BindingTypes, ModelDef reset_compdefs() @@ -39,21 +39,30 @@ end # Test the calls the macro will produce let calling_module = @__MODULE__ - global MyComposite + global MyComposite = Model() ccname = :testcomp ccid = ComponentId(calling_module, ccname) comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] - bindings = [DatumRef(Comp2, :par1) => 5, # bind Comp1.par1 to constant value of 5 - DatumRef(Comp2, :par1) => DatumRef(Comp1, :var1), # connect target Comp2.par1 to source Comp1.var1 - DatumRef(Comp3, :par1) => DatumRef(Comp2, :var1)] + bindings = [ + DatumReference(Comp2, :par1) => 5, # bind Comp1.par1 to constant value of 5 + DatumReference(Comp2, :par1) => DatumReference(Comp1, :var1), # connect target Comp2.par1 to source Comp1.var1 + DatumReference(Comp3, :par1) => DatumReference(Comp2, :var1) + ] - exports = [DatumRef(Comp1, :par1) => :c1p1, # i.e., export Comp1.par1 as :c1p1 - DatumRef(Comp2, :par2) => :c2p2, - DatumRef(Comp3, :var1) => :c3v1] + exports = [ + DatumReference(Comp1, :par1) => :c1p1, # i.e., export Comp1.par1 as :c1p1 + DatumReference(Comp2, :par2) => :c2p2, + DatumReference(Comp3, :var1) => :c3v1 + ] - MyComposite = NewModel(CompositeComponentDef(ccid, ccname, comps, bindings, exports)) + ccd = CompositeComponentDef(ccid, ccname, + Vector{AbstractComponentDef}(comps), + Vector{Pair{DatumReference, BindingTypes}}(bindings), + exports) + + MyComposite.md = ModelDef(ccd) nothing end From 91bf72fb0b3f444ca6b0eb89ff7bc56264ed0fa1 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 25 Oct 2018 18:04:29 -0700 Subject: [PATCH 12/81] WIP - Composite Components checkpoint --- src/core/build.jl | 9 +- src/core/connections.jl | 78 ++++--- src/core/defs.jl | 219 +++++++----------- src/core/instances.jl | 4 +- src/core/model.jl | 4 +- src/core/time.jl | 4 - src/core/types.jl | 27 ++- test/test_components.jl | 8 +- test/test_composite.jl | 2 + test/test_connectorcomp.jl | 8 +- test/test_dimensions.jl | 8 +- test/test_model_structure.jl | 4 +- test/test_model_structure_variabletimestep.jl | 4 +- test/test_parametertypes.jl | 24 +- test/test_replace_comp.jl | 19 +- test/test_variables_model_instance.jl | 18 +- wip/getprop_exploration.jl | 40 ---- 17 files changed, 199 insertions(+), 281 deletions(-) delete mode 100644 wip/getprop_exploration.jl diff --git a/src/core/build.jl b/src/core/build.jl index 984b92327..379925084 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -60,7 +60,7 @@ end _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) Instantiate a component `comp_def` in the model `md` and its variables (but not its parameters). -Return the resulting ComponentInstance. +Return the resulting ComponentInstanceVariables. """ function _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) comp_name = name(comp_def) @@ -87,12 +87,11 @@ end function build(m::Model) # Reference a copy in the ModelInstance to avoid changes underfoot - m.mi = build(copy(m.md)) + m.mi = build(deepcopy(m.md)) return nothing end function build(md::ModelDef) - add_connector_comps(md) # check if all parameters are set @@ -133,7 +132,7 @@ function build(md::ModelDef) # Make the external parameter connections for the hidden ConnectorComps. # Connect each :input2 to its associated backup value. - for (i, backup) in enumerate(md.backups) + for (i, backup) in enumerate(backups(md)) comp_name = connector_comp_name(i) param = external_param(md, backup) @@ -158,7 +157,7 @@ function build(md::ModelDef) first = first_period(md, comp_def) last = last_period(md, comp_def) - ci = ComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) + ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) add_comp!(mi, ci) end diff --git a/src/core/connections.jl b/src/core/connections.jl index 86e521ef6..d1d6dac1a 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -62,6 +62,12 @@ function _check_labels(md::ModelDef, comp_def::AbstractComponentDef, param_name: end end +@delegate backups(md::ModelDef) => ccd +backups(ccd::CompositeComponentDef) = ccd.backups + +@delegate add_backup!(md::ModelDef, obj) => ccd +add_backup!(ccd::CompositeComponentDef, obj) = push!(ccd.backups, obj) + """ connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) @@ -79,7 +85,7 @@ function connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext disconnect_param!(md, comp_name, param_name) conn = ExternalParameterConnection(comp_name, param_name, ext_param_name) - add_external_param_conn(md, conn) + add_external_param_conn!(md, conn) return nothing end @@ -171,7 +177,7 @@ function connect_param!(md::ModelDef, # println("connect($src_comp_name.$src_var_name => $dst_comp_name.$dst_par_name)") conn = InternalParameterConnection(src_comp_name, src_var_name, dst_comp_name, dst_par_name, ignoreunits, backup_param_name, offset=offset) - add_internal_param_conn(md, conn) + add_internal_param_conn!(md, conn) return nothing end @@ -233,11 +239,11 @@ the dictionary keys are strings that match the names of unset parameters in the function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T parameters = Dict(k => v for (k, v) in parameters) leftovers = unconnected_params(md) - external_params = md.external_params + external_params = external_params(md) for (comp_name, param_name) in leftovers # check whether we need to set the external parameter - if ! haskey(md.external_params, param_name) + if ! haskey(external_params, param_name) value = parameters[string(param_name)] param_dims = parameter_dimensions(md, comp_name, param_name) @@ -249,9 +255,11 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T nothing end -internal_param_conns(md::ModelDef) = md.internal_param_conns +@delegate external_param_conns(md::ModelDef) => ccd +external_param_conns(ccd::CompositeComponentDef) = ccd.external_param_conns -external_param_conns(md::ModelDef) = md.external_param_conns +@delegate internal_param_conns(md::ModelDef) => ccd +internal_param_conns(ccd::CompositeComponentDef) = ccd.internal_param_conns # Find internal param conns to a given destination component function internal_param_conns(md::ModelDef, dst_comp_name::Symbol) @@ -263,9 +271,11 @@ function external_param_conns(md::ModelDef, comp_name::Symbol) return filter(x -> x.comp_name == comp_name, external_param_conns(md)) end -function external_param(md::ModelDef, name::Symbol) +@delegate external_param(md::ModelDef, name::Symbol) => ccd + +function external_param(ccd::CompositeComponentDef, name::Symbol) try - return md.external_params[name] + return ccd.external_params[name] catch err if err isa KeyError error("$name not found in external parameter list") @@ -275,16 +285,22 @@ function external_param(md::ModelDef, name::Symbol) end end -function add_internal_param_conn(md::ModelDef, conn::InternalParameterConnection) - push!(md.internal_param_conns, conn) +@delegate add_internal_param_conn!(md::ModelDef, conn::InternalParameterConnection) => ccd + +function add_internal_param_conn!(ccd::CompositeComponentDef, conn::InternalParameterConnection) + push!(ccd.internal_param_conns, conn) end -function add_external_param_conn(md::ModelDef, conn::ExternalParameterConnection) - push!(md.external_param_conns, conn) +@delegate add_external_param_conn!(md::ModelDef, conn::ExternalParameterConnection) => ccd + +function add_external_param_conn!(ccd::CompositeComponentDef, conn::ExternalParameterConnection) + push!(ccd.external_param_conns, conn) end -function set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter) - md.external_params[name] = value +@delegate set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter) => ccd + +function set_external_param!(ccd::CompositeComponentDef, name::Symbol, value::ModelParameter) + ccd.external_params[name] = value end function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) @@ -294,7 +310,7 @@ end function set_external_param!(md::ModelDef, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; param_dims::Union{Nothing,Array{Symbol}} = nothing) if param_dims[1] == :time - value = convert(Array{md.number_type}, value) + value = convert(Array{number_type(md)}, value) num_dims = length(param_dims) values = get_timestep_array(md, eltype(value), num_dims, value) else @@ -332,9 +348,7 @@ end Add an array type parameter `name` with value `value` and `dims` dimensions to the model 'm'. """ -function set_external_array_param!(md::ModelDef, name::Symbol, value::AbstractArray, dims) - numtype = md.number_type - +function set_external_array_param!(md::ModelDef, name::Symbol, value::AbstractArray, dims) if !(typeof(value) <: Array{numtype}) numtype = number_type(md) # Need to force a conversion (simple convert may alias in v0.6) @@ -367,7 +381,7 @@ function update_param!(md::ModelDef, name::Symbol, value; update_timesteps = fal end function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true) - ext_params = md.external_params + ext_params = external_params(md) if ! haskey(ext_params, name) error("Cannot update parameter; $name not found in model's external parameters.") end @@ -399,11 +413,12 @@ end function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise_error) # Get original parameter - param = md.external_params[name] + param = external_param(md, name) # Check type of provided parameter if !(typeof(value) <: AbstractArray) error("Cannot update array parameter $name with a value of type $(typeof(value)).") + elseif !(eltype(value) <: eltype(param.values)) try value = convert(Array{eltype(param.values)}, value) @@ -427,7 +442,8 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise T = eltype(value) N = length(size(value)) new_timestep_array = get_timestep_array(md, T, N, value) - md.external_params[name] = ArrayModelParameter(new_timestep_array, param.dimensions) + set_external_param!(md, name, ArrayModelParameter(new_timestep_array, param.dimensions)) + elseif raise_error error("Cannot update timesteps; parameter $name is not a TimestepArray.") else @@ -462,7 +478,7 @@ end function add_connector_comps(md::ModelDef) - conns = md.internal_param_conns # we modify this, so we don't use functional API + conns = internal_param_conns(md) for comp_def in compdefs(md) comp_name = name(comp_def) @@ -474,7 +490,7 @@ function add_connector_comps(md::ModelDef) # println("Need connectors comps: $need_conn_comps") for (i, conn) in enumerate(need_conn_comps) - push!(md.backups, conn.backup) + add_backup!(md, conn.backup) num_dims = length(size(external_param(md, conn.backup).values)) @@ -492,17 +508,17 @@ function add_connector_comps(md::ModelDef) add_comp!(md, conn_comp_def, conn_comp_name, before=comp_name) # add a connection between src_component and the ConnectorComp - push!(conns, InternalParameterConnection(conn.src_comp_name, conn.src_var_name, - conn_comp_name, :input1, - conn.ignoreunits)) + add_internal_param_conn!(md, InternalParameterConnection(conn.src_comp_name, conn.src_var_name, + conn_comp_name, :input1, + conn.ignoreunits)) # add a connection between ConnectorComp and dst_component - push!(conns, InternalParameterConnection(conn_comp_name, :output, - conn.dst_comp_name, conn.dst_par_name, - conn.ignoreunits)) + add_internal_param_conn!(md, InternalParameterConnection(conn_comp_name, :output, + conn.dst_comp_name, conn.dst_par_name, + conn.ignoreunits)) # add a connection between ConnectorComp and the external backup data - push!(md.external_param_conns, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) + add_external_param_conn!(md, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) src_comp_def = compdef(md, conn.src_comp_name) set_param!(md, conn_comp_name, :first, first_period(md, src_comp_def)) @@ -512,7 +528,7 @@ function add_connector_comps(md::ModelDef) end # Save the sorted component order for processing - md.sorted_comps = _topological_sort(md) + # md.sorted_comps = _topological_sort(md) return nothing end diff --git a/src/core/defs.jl b/src/core/defs.jl index 347879cd3..178fd7aa1 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -18,13 +18,17 @@ function compdef(comp_name::Symbol) end end -compdefs(md::ModelDef) = values(md.comp_defs) +# TBD: Might need an option like `deep=True` to recursively descend through composites +compdefs(ccd::CompositeComponentDef) = values(ccd.comps_dict) +compkeys(ccd::CompositeComponentDef) = keys(ccd.comps_dict) +hascomp(ccd::CompositeComponentDef, comp_name::Symbol) = haskey(ccd.comps_dict, comp_name) +compdef(ccd::CompositeComponentDef, comp_name::Symbol) = ccd.comps_dict[comp_name] -compkeys(md::ModelDef) = keys(md.comp_defs) +@delegate compdefs(md::ModelDef) => ccd +@delegate compkeys(md::ModelDef) => ccd +@delegate hascomp(md::ModelDef, comp_name::Symbol) => ccd +@delegate compdef(md::ModelDef, comp_name::Symbol) => ccd -hascomp(md::ModelDef, comp_name::Symbol) = haskey(md.comp_defs, comp_name) - -compdef(md::ModelDef, comp_name::Symbol) = md.comp_defs[comp_name] function reset_compdefs(reload_builtins=true) empty!(_compdefs) @@ -36,12 +40,29 @@ function reset_compdefs(reload_builtins=true) end first_period(comp_def::LeafComponentDef) = comp_def.first -last_period(comp_def::LeafComponentDef) = comp_def.last +last_period(comp_def::LeafComponentDef) = comp_def.last -first_period(ccd::CompositeComponentDef) = length(ccd.comps) ? min([first_period(cd) for cd in ccd.comps]) : nothing -last_period(ccd::CompositeComponentDef) = length(ccd.comps) ? max([last_period(cd) for cd in ccd.comps]) : nothing +function first_period(ccd::CompositeComponentDef) + if length(ccd.comps_dict) > 0 + firsts = [first_period(cd) for cd in compdefs(ccd)] + if findfirst(isequal(nothing), firsts) == nothing # i.e., there are no `nothing`s + return min(Vector{Int}(firsts)...) + end + end + nothing # use model's first period +end + +function last_period(ccd::CompositeComponentDef) + if length(ccd.comps_dict) > 0 + lasts = [last_period(cd) for cd in compdefs(ccd)] + if findfirst(isequal(nothing), lasts) == nothing # i.e., there are no `nothing`s + return max(Vector{Int}(lasts)...) + end + end + nothing # use model's last period +end -first_period(md::ModelDef, comp_def::AbstractComponentDef) = first_period(comp_def) === nothing ? time_labels(md)[1] : first_period(comp_def) +first_period(md::ModelDef, comp_def::AbstractComponentDef) = first_period(comp_def) === nothing ? time_labels(md)[1] : first_period(comp_def) last_period(md::ModelDef, comp_def::AbstractComponentDef) = last_period(comp_def) === nothing ? time_labels(md)[end] : last_period(comp_def) # Return the module object for the component was defined in @@ -65,7 +86,7 @@ number_type(md::ModelDef) = md.number_type @delegate numcomponents(md::ModelDef) => ccd -numcomponents(ccd::CompositeComponentDef) = length(ccd.comps) +numcomponents(ccd::CompositeComponentDef) = length(ccd.comps_dict) function dump_components() for comp in compdefs() @@ -101,22 +122,24 @@ function new_comp(comp_id::ComponentId, verbose::Bool=true) end """ - delete!(m::ModelDef, component::Symbol + delete!(m::ModelDef, component::Symbol) Delete a `component` by name from a model definition `m`. """ -function Base.delete!(md::ModelDef, comp_name::Symbol) - if ! haskey(md.comp_defs, comp_name) - error("Cannot delete '$comp_name' from model; component does not exist.") +@delegate Base.delete!(md::ModelDef, comp_name::Symbol) => ccd + +function Base.delete!(ccd::CompositeComponentDef, comp_name::Symbol) + if ! hascomp(ccd, comp_name) + error("Cannot delete '$comp_name': component does not exist.") end - delete!(md.comp_defs, comp_name) + delete!(ccd.comps_dict, comp_name) ipc_filter = x -> x.src_comp_name != comp_name && x.dst_comp_name != comp_name - filter!(ipc_filter, md.internal_param_conns) + filter!(ipc_filter, ccd.internal_param_conns) epc_filter = x -> x.comp_name != comp_name - filter!(epc_filter, md.external_param_conns) + filter!(epc_filter, ccd.external_param_conns) end # @@ -135,7 +158,7 @@ dimensions(cd::LeafComponentDef) = values(cd.dimensions) function dimensions(ccd::CompositeComponentDef) dims = Vector{DimensionDef}() - for cd in components(ccd) + for cd in compdefs(ccd) append!(dims, dimensions(cd)) end @@ -298,7 +321,8 @@ end # Parameters # -external_params(md::ModelDef) = md.external_params +@delegate external_params(md::ModelDef) => ccd +external_params(ccd::CompositeComponentDef) = ccd.external_params function addparameter(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit, default) p = DatumDef(name, datatype, dimensions, description, unit, :parameter, default) @@ -319,9 +343,10 @@ parameters(comp_def::LeafComponentDef) = values(comp_def.parameters) function parameters(ccd::CompositeComponentDef) params = Vector{DatumDef}() - for cd in ccd.comps - append!(params, collect(parameters(cd))) + for cd in values(ccd.comps_dict) + append!(params, parameters(cd)) end + return params end @@ -351,6 +376,7 @@ function parameter(comp_def::LeafComponentDef, name::Symbol) end end +# TBD: should this find the parameter regardless of whether it's exported? function parameter(ccd::CompositeComponentDef, name::Symbol) try return ccd.external_params[name] @@ -374,7 +400,7 @@ end Set the parameter `name` of a component `comp_name` in a model `m` to a given `value`. The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a -list of the dimension names ofthe provided data, and will be used to check that +list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) @@ -443,9 +469,10 @@ variables(comp_def::LeafComponentDef) = values(comp_def.variables) function variables(ccd::CompositeComponentDef) vars = Vector{DatumDef}() - for cd in ccd.comps - append!(vars, collect(variables(cd))) + for cd in values(ccd.comps_dict) + append!(vars, variables(cd)) end + return vars end variables(comp_id::ComponentId) = variables(compdef(comp_id)) @@ -525,6 +552,10 @@ end const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} +function _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::AbstractComponentDef) + md.ccd.comps_dict[comp_name] = comp_def +end + """ add_comp!(md::ModelDef, comp_def::LeafComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) @@ -570,7 +601,7 @@ function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; set_run_period!(comp_def, first, last) if before === nothing && after === nothing - md.comp_defs[comp_name] = comp_def # just add it to the end + _append_comp!(md, comp_name, comp_def) # just add it to the end else new_comps = OrderedDict{Symbol, AbstractComponentDef}() @@ -579,11 +610,11 @@ function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; error("Component to add before ($before) does not exist") end - for i in compkeys(md) - if i == before + for k in compkeys(md) + if k == before new_comps[comp_name] = comp_def end - new_comps[i] = md.comp_defs[i] + new_comps[k] = compdef(md, k) end else # after !== nothing, since we've handled all other possibilities above @@ -591,16 +622,16 @@ function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; error("Component to add before ($before) does not exist") end - for i in compkeys(md) - new_comps[i] = md.comp_defs[i] - if i == after + for k in compkeys(md) + new_comps[k] = compdef(md, k) + if k == after new_comps[comp_name] = comp_def end end end - md.comp_defs = new_comps - # println("md.comp_defs: $(md.comp_defs)") + md.ccd.comps_dict = new_comps + # println("md.ccd.comp_defs: $(md.ccd.comp_defs)") end # Set parameters to any specified defaults @@ -647,13 +678,13 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) - if ! haskey(md.comp_defs, comp_name) + if ! hascomp(md, comp_name) error("Cannot replace '$comp_name'; component not found in model.") end # Get original position if new before or after not specified if before === nothing && after === nothing - comps = collect(keys(md.comp_defs)) + comps = collect(compkeys(md)) n = length(comps) if n > 1 idx = findfirst(isequal(comp_name), comps) @@ -666,7 +697,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end # Get original first and last if new run period not specified - old_comp = md.comp_defs[comp_name] + old_comp = compdef(md, comp_name) first = first === nothing ? old_comp.first : first last = last === nothing ? old_comp.last : last @@ -690,7 +721,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end # Check outgoing variables - outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_name == comp_name, md.internal_param_conns)) + outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_name == comp_name, internal_param_conns(md))) old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) new_vars = new_comp.variables if !_compare_datum(new_vars, old_vars) @@ -715,10 +746,10 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end end end - filter!(epc -> !(epc in remove), md.external_param_conns) + filter!(epc -> !(epc in remove), external_param_conns(md)) - # Delete the old component from comp_defs, leaving the existing parameter connections - delete!(md.comp_defs, comp_name) + # Delete the old component from comps_dict, leaving the existing parameter connections + delete!(md.ccd.comps_dict, comp_name) else # Delete the old component and all its internal and external parameter connections delete!(md, comp_name) @@ -728,111 +759,17 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com add_comp!(md, comp_id, comp_name; first=first, last=last, before=before, after=after) end +# +# TBD: we can probably remove most of this copying code and just rely on deepcopy(). +# + """ copy_comp_def(comp_def::AbstractComponentDef, comp_name::Symbol) -Create a mostly-shallow copy of `comp_def` (named `comp_name`), but make a deep copy of its -ComponentId so we can rename the copy without affecting the original. +Copy the given `comp_def`, naming the copy `comp_name`. """ -function copy_comp_def(comp_def::LeafComponentDef, comp_name::Symbol) - comp_id = comp_def.comp_id - obj = LeafComponentDef(comp_id) - - # Use the comp_id as is, since this identifies the run_timestep function, but - # use an alternate name to reference it in the model's component list. - obj.name = comp_name - - obj.variables = comp_def.variables - obj.parameters = comp_def.parameters - obj.dimensions = comp_def.dimensions - obj.first = comp_def.first - obj.last = comp_def.last - - return obj -end - -function copy_comp_def(ccd::CompositeComponentDef, comp_name::Symbol) - comp_id = ccd.comp_id - obj = CompositeComponentDef(comp_id) +function copy_comp_def(comp_def::AbstractComponentDef, comp_name::Symbol) + obj = deepcopy(comp_def) obj.name = comp_name - - append!(obj.comps, [copy_comp_def(cd) for cd in ccd.comps]) - - append!(obj.bindings, ccd.bindings) # TBD: need to deepcopy these? - append!(obj.exports, ccd.exports) # TBD: ditto? - - # TBD: what to do with these? - # internal_param_conns::Vector{InternalParameterConnection} - # external_param_conns::Vector{ExternalParameterConnection} - - # Names of external params that the ConnectorComps will use as their :input2 parameters. - append!(obj.backups, ccd.backups) - - external_params::Dict{Symbol, ModelParameter} - - if ccd.sorted_comps === nothing - obj.sorted_comps = nothing - else - append!(obj.sorted_comps, ccd.sorted_comps) - end - return obj end - -""" - copy_external_params(md::ModelDef) - -Make copies of ModelParameter subtypes representing external parameters of model `md`. -This is used both in the copy() function below, and in the MCS subsystem -to restore values between trials. - -""" -function copy_external_params(md::ModelDef) - external_params = Dict{Symbol, ModelParameter}(key => copy(obj) for (key, obj) in md.external_params) - return external_params -end - -Base.copy(obj::ScalarModelParameter{T}) where T = ScalarModelParameter{T}(copy(obj.value)) - -Base.copy(obj::ArrayModelParameter{T}) where T = ArrayModelParameter{T}(copy(obj.values), obj.dimensions) - -function Base.copy(obj::TimestepVector{T_ts, T}) where {T_ts, T} - return TimestepVector{T_ts, T}(copy(obj.data)) -end - -function Base.copy(obj::TimestepMatrix{T_ts, T}) where {T_ts, T} - return TimestepMatrix{T_ts, T}(copy(obj.data)) -end - -function Base.copy(obj::TimestepArray{T_ts, T, N}) where {T_ts, T, N} - return TimestepArray{T_ts, T, N}(copy(obj.data)) -end - -""" - copy(md::ModelDef) - -Create a copy of a ModelDef `md` object that is not entirely shallow, nor completely deep. -The aim is to copy the full structure, reusing references to immutable elements. -""" -function Base.copy(md::ModelDef) - mdcopy = ModelDef(md.number_type) - mdcopy.module_name = md.module_name - - merge!(mdcopy.comp_defs, md.comp_defs) - - mdcopy.dimensions = deepcopy(md.dimensions) - - # These are vectors of immutable structs, so we can (shallow) copy them safely - mdcopy.internal_param_conns = copy(md.internal_param_conns) - mdcopy.external_param_conns = copy(md.external_param_conns) - - # Names of external params that the ConnectorComps will use as their :input2 parameters. - mdcopy.backups = copy(md.backups) - mdcopy.external_params = copy_external_params(md) - - mdcopy.sorted_comps = md.sorted_comps === nothing ? nothing : copy(md.sorted_comps) - - mdcopy.is_uniform = md.is_uniform - - return mdcopy -end diff --git a/src/core/instances.jl b/src/core/instances.jl index bdb3f78e6..997f3a255 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -16,7 +16,7 @@ compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) @delegate compdef(cci::CompositeComponentInstance) => leaf """ - name(ci::ComponentInstance) + name(ci::AbstractComponentInstance) Return the name of the component `ci`. """ @@ -189,7 +189,7 @@ Return the `ComponentInstanceParameters` for `comp_name` in ModelInstance 'mi'. parameters(mi::ModelInstance, comp_name::Symbol) = parameters(compinstance(mi, comp_name)) """ - parameters(ci::ComponentInstance) + parameters(ci::AbstractComponentInstance) Return an iterator over the parameters in `ci`. """ diff --git a/src/core/model.jl b/src/core/model.jl index 9ea4f27b8..25c8778f9 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -92,8 +92,8 @@ function set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, set_external_param!(m.md, name, value; param_dims = param_dims) end -function add_internal_param_conn(m::Model, conn::InternalParameterConnection) - add_internal_param_conn(m.md, conn) +function add_internal_param_conn!(m::Model, conn::InternalParameterConnection) + add_internal_param_conn!(m.md, conn) decache(m) end diff --git a/src/core/time.jl b/src/core/time.jl index 6144f324d..ea57c537d 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -165,7 +165,6 @@ end # Get a timestep array of type T with N dimensions. Time labels will match those from the time dimension in md function get_timestep_array(md::ModelDef, T, N, value) - if isuniform(md) first, stepsize = first_and_step(md) return TimestepArray{FixedTimestep{first, stepsize}, T, N}(value) @@ -177,9 +176,6 @@ end const AnyIndex = Union{Int, Vector{Int}, Tuple, Colon, OrdinalRange} -# TBD: can it be reduced to this? -# const AnyIndex = Union{Int, AbstractRange} - # # 3b. TimestepVector # diff --git a/src/core/types.jl b/src/core/types.jl index d146502e8..1fcea5178 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -182,7 +182,7 @@ end struct ExternalParameterConnection <: AbstractConnection comp_name::Symbol param_name::Symbol # name of the parameter in the component - external_param::Symbol # name of the parameter stored in md.external_params + external_param::Symbol # name of the parameter stored in md.ccd.external_params end # @@ -231,7 +231,7 @@ mutable struct DatumDef <: NamedDef end -mutable struct DimensionDef <: NamedDef +struct DimensionDef <: NamedDef name::Symbol end @@ -278,7 +278,7 @@ global const BindingTypes = Union{Int, Float64, DatumReference} mutable struct CompositeComponentDef <: AbstractComponentDef comp_id::Union{Nothing, ComponentId} # allow anonynous top-level CompositeComponentDefs (must be referenced by a ModelDef) name::Union{Nothing, Symbol} - comps::Vector{<:AbstractComponentDef} + comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} exports::Vector{Pair{DatumReference, Symbol}} @@ -292,7 +292,8 @@ mutable struct CompositeComponentDef <: AbstractComponentDef sorted_comps::Union{Nothing, Vector{Symbol}} - function CompositeComponentDef(comp_id::ComponentId, name::Symbol, + function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}, + comp_name::Union{Nothing, Symbol}, comps::Vector{<:AbstractComponentDef}, bindings::Vector{Pair{DatumReference, BindingTypes}}, exports::Vector{Pair{DatumReference, Symbol}}) @@ -302,7 +303,9 @@ mutable struct CompositeComponentDef <: AbstractComponentDef backups = Vector{Symbol}() sorted_comps = nothing - return new(comp_id, name, comps, bindings, exports, + comps_dict = OrderedDict{Symbol, AbstractComponentDef}([name(cd) => cd for cd in comps]) + + return new(comp_id, comp_name, comps_dict, bindings, exports, internal_param_conns, external_param_conns, backups, external_params, sorted_comps) end @@ -312,7 +315,7 @@ mutable struct CompositeComponentDef <: AbstractComponentDef comps = Vector{AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() - return new(comp_id, comp_name, comps, bindings, exports) + return CompositeComponentDef(comp_id, comp_name, comps, bindings, exports) end function CompositeComponentDef() @@ -448,16 +451,16 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com end end -mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance +mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} <: AbstractComponentInstance # TV, TP, and dim_dict are computed by aggregating all the vars and params from the CompositeComponent's - # sub-components. Might be simplest to implement using a LeafComponentInstance that holds all the - # "summary" values and references, the init and run_timestep funcs, and a vector of sub-components. + # sub-components. We use a LeafComponentInstance to holds all the "summary" values and references. leaf::LeafComponentInstance{TV, TP} comp_dict::OrderedDict{Symbol, <: AbstractComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} - clocks::Union{Nothing, Vector{Clock}} + clocks::Vector{Clock} function CompositeComponentInstance{TV, TP}( comp_def::CompositeComponentDef, vars::TV, pars::TP, @@ -467,7 +470,7 @@ mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, TP < comps_dict = OrderedDict{Symbol, <: AbstractComponentInstance}() firsts = Vector{Int}() lasts = Vector{Int}() - clocks = nothing + clocks = Vector{Clock}() return new{TV, TP}(leaf, comp_dict, firsts, lasts, clocks) end end @@ -503,7 +506,7 @@ mutable struct Model # Create a copy of a model, e.g., to create marginal models function Model(m::Model) - return new(copy(m.md), nothing) + return new(deepcopy(m.md), nothing) end end diff --git a/test/test_components.jl b/test/test_components.jl index 893e7a90b..a71a8dbcb 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -124,7 +124,7 @@ cd = compdef(testcomp1) m = Model() set_dimension!(m, :time, 2001:2005) add_comp!(m, testcomp1, :C) # Don't set the first and last values here -cd = m.md.comp_defs[:C] # Get the component definition in the model +cd = compdef(m.md, :C) # Get the component definition in the model @test cd.first === nothing # First and last values should still be nothing because they were not explicitly set @test cd.last === nothing @@ -135,7 +135,7 @@ ci = m.mi.components[:C] # Get the component instance @test ci.last == 2005 set_dimension!(m, :time, 2005:2020) # Reset the time dimension -cd = m.md.comp_defs[:C] # Get the component definition in the model +cd = compdef(m.md, :C) # Get the component definition in the model @test cd.first === nothing # First and last values should still be nothing @test cd.last === nothing @@ -151,7 +151,7 @@ ci = m.mi.components[:C] # Get the component instance m = Model() set_dimension!(m, :time, 2000:2100) add_comp!(m, testcomp1, :C; first=2010, last=2090) # Give explicit first and last values for the component -cd = m.md.comp_defs[:C] # Get the component definition in the model +cd = compdef(m.md, :C) # Get the component definition in the model @test cd.first == 2010 # First and last values are defined in the comp def because they were explicitly given @test cd.last == 2090 @@ -162,7 +162,7 @@ ci = m.mi.components[:C] # Get the component instance @test ci.last == 2090 set_dimension!(m, :time, 2000:2200) # Reset the time dimension -cd = m.md.comp_defs[:C] # Get the component definition in the model +cd = compdef(m.md, :C) # Get the component definition in the model @test cd.first == 2010 # First and last values should still be the same @test cd.last == 2090 diff --git a/test/test_composite.jl b/test/test_composite.jl index d8569d3d5..78a523276 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -39,6 +39,7 @@ end # Test the calls the macro will produce let calling_module = @__MODULE__ + # calling_module = TestComposite global MyComposite = Model() ccname = :testcomp @@ -63,6 +64,7 @@ let calling_module = @__MODULE__ exports) MyComposite.md = ModelDef(ccd) + set_dimension!(MyComposite, :time, 2005:2020) nothing end diff --git a/test/test_connectorcomp.jl b/test/test_connectorcomp.jl index aab7e557f..3f61678ab 100644 --- a/test/test_connectorcomp.jl +++ b/test/test_connectorcomp.jl @@ -41,7 +41,7 @@ connect_param!(model1, :Long, :x, :Short, :b, zeros(length(years))) run(model1) @test length(components(model1.mi)) == 3 # ConnectorComp is added to the list of components in the model isntance -@test length(model1.md.comp_defs) == 2 # The ConnectorComp shows up in the model instance but not the model definition +@test length(compdefs(model1.md)) == 2 # The ConnectorComp shows up in the model instance but not the model definition b = model1[:Short, :b] x = model1[:Long, :x] @@ -83,7 +83,7 @@ connect_param!(model2, :Long, :x, :Short, :b, zeros(length(years_variable))) run(model2) @test length(components(model2.mi)) == 3 -@test length(model2.md.comp_defs) == 2 # The ConnectorComp shows up in the model instance but not the model definition +@test length(compdefs(model2.md)) == 2 # The ConnectorComp shows up in the model instance but not the model definition b = model2[:Short, :b] x = model2[:Long, :x] @@ -137,7 +137,7 @@ connect_param!(model3, :Long_multi, :x, :Short_multi, :b, zeros(length(years), l run(model3) @test length(components(model3.mi)) == 3 -@test length(model3.md.comp_defs) == 2 # The ConnectorComp shows up in the model instance but not the model definition +@test length(compdefs(model3.md)) == 2 # The ConnectorComp shows up in the model instance but not the model definition b = model3[:Short_multi, :b] x = model3[:Long_multi, :x] @@ -173,7 +173,7 @@ connect_param!(model4, :Long_multi=>:x, :Short_multi=>:b, zeros(length(years), l run(model4) @test length(components(model4.mi)) == 3 -@test length(model4.md.comp_defs) == 2 # The ConnectorComp shows up in the model instance but not the model definition +@test length(compdefs(model4.md)) == 2 # The ConnectorComp shows up in the model instance but not the model definition b = model4[:Short_multi, :b] x = model4[:Long_multi, :x] diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index 472853878..7c82c2f51 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -97,8 +97,8 @@ add_comp!(m, foo2; first = 2005, last = 2095) (:warn, "Redefining dimension :time"), set_dimension!(m, :time, 1990:2200) ) -@test m.md.comp_defs[:foo2].first == 2005 -@test m.md.comp_defs[:foo2].last == 2095 +@test first_period(compdef(m.md, :foo2)) == 2005 +@test last_period(compdef(m.md, :foo2)) == 2095 # Test parameter connections @test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long @@ -111,7 +111,7 @@ set_param!(m, :foo2, :x, 2005:2095) # Shouldn't throw an error (:warn, "Resetting foo2 component's last timestep to 2050"), set_dimension!(m, :time, 2010:2050) ) -@test m.md.comp_defs[:foo2].first == 2010 -@test m.md.comp_defs[:foo2].last == 2050 +@test first_period(compdef(m.md, :foo2)) == 2010 +@test last_period(compdef(m.md, :foo2)) == 2050 end #module diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 87829f946..284e0e078 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -116,9 +116,9 @@ a = collect(keys(time)) ################################ @test_throws ErrorException delete!(m, :D) -@test length(m.md.internal_param_conns) == 2 +@test length(internal_param_conns(m.md)) == 2 delete!(m, :A) -@test length(m.md.internal_param_conns) == 1 +@test length(internal_param_conns(m.md)) == 1 @test !(:A in compdefs(m)) @test length(compdefs(m)) == 2 diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 7379a7f61..130903b89 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -117,9 +117,9 @@ a = collect(keys(time)) ################################ @test_throws ErrorException delete!(m, :D) -@test length(m.md.internal_param_conns) == 2 +@test length(internal_param_conns(m.md)) == 2 delete!(m, :A) -@test length(m.md.internal_param_conns) == 1 +@test length(internal_param_conns(m.md)) == 1 @test !(:A in compdefs(m)) @test length(compdefs(m)) == 2 diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index c57e2e77f..bee08a154 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -133,7 +133,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) ) update_param!(m, :x, [4, 5, 6], update_timesteps = false) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2000, 1, LAST} where LAST, Float64, 1} @test x.values.data == [4., 5., 6.] run(m) @@ -141,7 +141,7 @@ run(m) @test m[:MyComp2, :y][2] == 6 # 2002 update_param!(m, :x, [2, 3, 4], update_timesteps = true) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2001, 1, LAST} where LAST, Float64, 1} @test x.values.data == [2., 3., 4.] run(m) @@ -163,7 +163,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) ) update_param!(m, :x, [4, 5, 6], update_timesteps = false) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2005, 2020)}, Float64, 1} @test x.values.data == [4., 5., 6.] run(m) @@ -171,7 +171,7 @@ run(m) @test m[:MyComp2, :y][2] == 6 # 2020 update_param!(m, :x, [2, 3, 4], update_timesteps = true) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Float64, 1} @test x.values.data == [2., 3., 4.] run(m) @@ -192,7 +192,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) ) update_params!(m, Dict(:x=>[2, 3, 4]), update_timesteps = true) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Float64, 1} @test x.values.data == [2., 3., 4.] run(m) @@ -212,7 +212,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) @test_throws ErrorException update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = false) update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = true) -x = m.md.external_params[:x] +x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, LAST} where LAST, Float64, 1} @test x.values.data == [2., 3., 4., 5., 6.] @@ -240,10 +240,10 @@ set_param!(m, :MyComp3, :z, 0) @test_throws ErrorException update_param!(m, :x, [1, 2, 3, 4]) # Will throw an error because size @test_throws ErrorException update_param!(m, :y, [10, 15], update_timesteps=true) # Not a timestep array update_param!(m, :y, [10, 15]) -@test m.md.external_params[:y].values == [10., 15.] +@test external_param(m.md, :y).values == [10., 15.] @test_throws ErrorException update_param!(m, :z, 1, update_timesteps=true) # Scalar parameter update_param!(m, :z, 1) -@test m.md.external_params[:z].value == 1 +@test external_param(m.md, :z).value == 1 # Reset the time dimensions @test_logs( @@ -253,9 +253,9 @@ update_param!(m, :z, 1) ) update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0), update_timesteps=true) # Won't error when updating from a dictionary -@test m.md.external_params[:x].values isa Mimi.TimestepArray{Mimi.FixedTimestep{2005,1},Float64,1} -@test m.md.external_params[:x].values.data == [3.,4.,5.] -@test m.md.external_params[:y].values == [10.,20.] -@test m.md.external_params[:z].value == 0 +@test external_param(m.md, :x).values isa Mimi.TimestepArray{Mimi.FixedTimestep{2005,1},Float64,1} +@test external_param(m.md, :x).values.data == [3.,4.,5.] +@test external_param(m.md, :y).values == [10.,20.] +@test external_param(m.md, :z).value == 0 end #module diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 9747364e5..5b053c78f 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -3,7 +3,7 @@ module TestReplaceComp using Test using Mimi import Mimi: - reset_compdefs + reset_compdefs, compdefs reset_compdefs() @@ -66,7 +66,7 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection with a parameter with a time dimension @test_throws ErrorException replace_comp!(m, bad1, :second) # Cannot make reconnections because :x in bad1 has different dimensions replace_comp!(m, bad1, :second, reconnect = false) # Can replace without reconnecting -@test m.md.comp_defs[:second].comp_id.comp_name == :bad1 # Successfully replaced +@test name(compdef(m.md, :second)) == :bad1 # Successfully replaced # 3. Test bad internal outgoing variable @@ -78,7 +78,7 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection from a variable with a time dimension @test_throws ErrorException replace_comp!(m, bad2, :first) # Cannot make reconnections because bad2 does not have a variable :y replace_comp!(m, bad2, :first, reconnect = false) # Can replace without reconnecting -@test m.md.comp_defs[:first].comp_id.comp_name == :bad2 # Successfully replaced +@test name(compdef(m.md, :first)) == :bad2 # Successfully replaced # 4. Test bad external parameter name @@ -91,9 +91,10 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs (:warn, r".*parameter x no longer exists in component.*") replace_comp!(m, bad3, :X) -@test m.md.comp_defs[:X].comp_id.comp_name == :bad3 # The replacement was still successful -@test length(m.md.external_param_conns) == 0 # The external paramter connection was removed -@test length(m.md.external_params) == 1 # The external parameter still exists +@test name(compdef(m.md, :X)) == :bad3 # The replacement was still successful +#external_param_conns(md, comp_name) +@test length(external_param_conns(m.md)) == 0 # The external paramter connection was removed +@test length(external_params(m.md)) == 1 # The external parameter still exists # 5. Test bad external parameter dimensions @@ -129,10 +130,12 @@ set_dimension!(m, :time, 2000:2005) add_comp!(m, X, :c1) add_comp!(m, X, :c2) add_comp!(m, X, :c3) + replace_comp!(m, X_repl, :c3) # test replacing the last component -@test collect(values(m.md.comp_defs))[3].comp_id.comp_name == :X_repl +@test compdef(m.md, :c3) == X_repl + replace_comp!(m, X_repl, :c2) # test replacing not the last one -@test collect(values(m.md.comp_defs))[2].comp_id.comp_name == :X_repl +@test compdef(m.md, :c2) == X_repl end # module \ No newline at end of file diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index 80115e78c..3f1dd6685 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -5,7 +5,9 @@ using Test import Mimi: reset_compdefs, variable_names, compinstance, get_var_value, get_param_value, - set_param_value, set_var_value, dim_count, dim_key_dict, dim_value_dict, compdef + set_param_value, set_var_value, dim_count, dim_key_dict, dim_value_dict, compdef, + AbstractComponentInstance, AbstractComponentDef, TimestepArray, ComponentInstanceParameters, + ComponentInstanceVariables reset_compdefs() @@ -40,29 +42,29 @@ cdef = compdef(ci) citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md -@test typeof(ci) <: Mimi.ComponentInstance && ci == mi.components[:testcomp1] -@test typeof(cdef) <: Mimi.LeafComponentDef && cdef == compdef(ci.comp_id) +@test typeof(ci) <: AbstractComponentInstance && ci == compinstance(mi, :testcomp1) +@test typeof(cdef) <: AbstractComponentDef && cdef == compdef(ci.comp_id) @test name(ci) == :testcomp1 -@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) == Mimi.ComponentInstance +@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: AbstractComponentInstance #test convenience functions that can be called with name symbol param_value = get_param_value(ci, :par1) -@test typeof(param_value)<: Mimi.TimestepArray +@test typeof(param_value)<: TimestepArray @test_throws ErrorException get_param_value(ci, :missingpar) var_value = get_var_value(ci, :var1) @test_throws ErrorException get_var_value(ci, :missingvar) -@test typeof(var_value) <: Mimi.TimestepArray +@test typeof(var_value) <: TimestepArray params = parameters(mi, :testcomp1) params2 = parameters(mi, :testcomp1) -@test typeof(params) <: Mimi.ComponentInstanceParameters +@test typeof(params) <: ComponentInstanceParameters @test params == params2 vars = variables(mi, :testcomp1) vars2 = variables(ci) -@test typeof(vars) <: Mimi.ComponentInstanceVariables +@test typeof(vars) <: ComponentInstanceVariables @test vars == vars2 @test dim_count(mi, :time) == 20 diff --git a/wip/getprop_exploration.jl b/wip/getprop_exploration.jl deleted file mode 100644 index c49acd316..000000000 --- a/wip/getprop_exploration.jl +++ /dev/null @@ -1,40 +0,0 @@ - -module test - -struct ComponentInstanceParameters{T <: NamedTuple} - nt::T -end - -struct ComponentInstanceVariables{T <: NamedTuple} - nt::T -end - -struct ComponentInstance{V <: NamedTuple, P <: NamedTuple} - vars::ComponentInstanceVariables{V} - params::ComponentInstanceParameters{P} -end - -function ComponentInstanceParameters(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceParameters{NT}(NT(values)) -end - -@inline function Base.getproperty(obj::ComponentInstanceParameters{T}, name::Symbol) where {T} - nt = getfield(obj, :nt) - return fieldtype(T, name) <: Ref ? getproperty(nt, name)[] : getproperty(nt, name) -end - - -using BenchmarkTools - -ci = ComponentInstanceParameters((a=1., b=2.)) - -foo(ci) = ci.a + ci.b - -println("@btime ci.a + ci.b") -@btime foo($ci) - -function run_timestep(p, v, d, t) -end - -end # module From 3698440da78c02de4fd992694c5810ff0cd9caf1 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 14 Nov 2018 14:32:59 -0800 Subject: [PATCH 13/81] WIP - Saved before switching to alternative approach --- src/core/build.jl | 45 +++++++++++++++++++++++++++++++++++++++++---- src/core/defs.jl | 35 +++++++++++++++++++++++++++++++---- src/core/types.jl | 18 +++++++++++++----- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 379925084..44d1508a8 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -85,12 +85,39 @@ function save_dim_dict_reference(mi::ModelInstance) return nothing end +# Return a built CompositeComponentInstance by recursively building +# all sub-components. +function build(ccd::CompositeComponentDef) + comps = Vector{T <: AbstractComponentInstance} + + for cd in compdefs(ccd) + ci = build(cd) + push!(comps, ci) + end + + # TBD: using ccd.exports, create the vars and params lists for the composite + vars = [] + pars = [] + + cci = CompositeComponentInstance{typeof(vars), typeof(pars)}(ccd, vars, pars, name(ccd)) + return cci +end + +# Return a built LeafComponentInstance +function build(lcd::LeafComponentDef) + vars = [] + pars = [] + lci = LeafComponentInstance{typeof(vars), typeof(pars)}(lcd, vars, pars, name(comp_def); is_composite=false) + return lci +end + function build(m::Model) # Reference a copy in the ModelInstance to avoid changes underfoot m.mi = build(deepcopy(m.md)) return nothing end +# TBD: this functionality needs to move to the build(ccd) and build(lcd) functions above function build(md::ModelDef) add_connector_comps(md) @@ -142,6 +169,10 @@ function build(md::ModelDef) mi = ModelInstance(md) + # Create a vector of ci instances in this following loop, then generate a + # CompositeComponentInstance from the vector. + comps = Vector{T <: AbstractComponentInstance} + # instantiate parameters for comp_def in comp_defs comp_name = name(comp_def) @@ -154,11 +185,17 @@ function build(md::ModelDef) ptypes = Tuple{map(typeof, pvals)...} pars = ComponentInstanceParameters(pnames, ptypes, pvals) - first = first_period(md, comp_def) - last = last_period(md, comp_def) + # first = first_period(md, comp_def) + # last = last_period(md, comp_def) + + # ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) + ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, comp_name) + + push!(comps, ci) + # add_comp!(mi, ci) + end - ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) - add_comp!(mi, ci) + for ci{TV, TP} in comps end save_dim_dict_reference(mi) diff --git a/src/core/defs.jl b/src/core/defs.jl index 178fd7aa1..250c8fe08 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -349,7 +349,6 @@ function parameters(ccd::CompositeComponentDef) return params end - """ parameters(comp_id::ComponentId) @@ -357,6 +356,8 @@ Return a list of the parameter definitions for `comp_id`. """ parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) +parameters(dr::DatumReference) = parameters(dr.comp_id) + """ parameter_names(md::ModelDef, comp_name::Symbol) @@ -368,6 +369,8 @@ parameter_names(comp_def::AbstractComponentDef) = [name(param) for param in para parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) +parameter(dr::DatumReference, name::Symbol) = parameter(compdef(dr.comp_id), name) + function parameter(comp_def::LeafComponentDef, name::Symbol) try return comp_def.parameters[name] @@ -477,6 +480,8 @@ end variables(comp_id::ComponentId) = variables(compdef(comp_id)) +variables(dr::DatumReference) = variables(dr.comp_id) + function variable(comp_def::LeafComponentDef, var_name::Symbol) try return comp_def.variables[var_name] @@ -489,6 +494,8 @@ variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), va variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(md, comp_name), var_name) +variable(dr::DatumReference, name::Symbol) = variable(compdef(dr.comp_id), name) + """ variable_names(md::ModelDef, comp_name::Symbol) @@ -511,9 +518,29 @@ end # Add a variable to a LeafComponentDef function addvariable(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit) - v = DatumDef(name, datatype, dimensions, description, unit, :variable) - comp_def.variables[name] = v - return v + var_def = DatumDef(name, datatype, dimensions, description, unit, :variable) + comp_def.variables[name] = var_def + return var_def +end + +# CompositeComponents have no vars of their own, only references to vars in +# components contained within. +function addvariable(comp_def::CompositeComponentDef, var_def::DatumDef, name::Symbol) + comp_def.variables[name] = var_def + return var_def +end + +""" + addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) + +Add all exported variables to a CompositeComponentDef. +""" +function addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) + for (dr, exp_name) in exports + #comp_def.variables[name] = var_def + + addvariable(comp_def, variable(comp_def, name(variable)), exp_name) + end end # Add a variable to a LeafComponentDef referenced by ComponentId diff --git a/src/core/types.jl b/src/core/types.jl index 1fcea5178..39fb25ea7 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -276,7 +276,8 @@ end global const BindingTypes = Union{Int, Float64, DatumReference} mutable struct CompositeComponentDef <: AbstractComponentDef - comp_id::Union{Nothing, ComponentId} # allow anonynous top-level CompositeComponentDefs (must be referenced by a ModelDef) + leaf::LeafComponentDef # a leaf component that simulates a single component for this composite + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level CompositeComponentDefs (must be referenced by a ModelDef) name::Union{Nothing, Symbol} comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} @@ -305,9 +306,16 @@ mutable struct CompositeComponentDef <: AbstractComponentDef comps_dict = OrderedDict{Symbol, AbstractComponentDef}([name(cd) => cd for cd in comps]) - return new(comp_id, comp_name, comps_dict, bindings, exports, + self = new(comp_id, comp_name, comps_dict, bindings, exports, internal_param_conns, external_param_conns, backups, external_params, sorted_comps) + + # for (dr, exp_name) in exports + # #comp_def.variables[name] = var_def + # addvariable(comp_def, variable(comp_def, name(variable)), exp_name) + # end + + return self end function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}, @@ -410,8 +418,8 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com variables::TV parameters::TP dim_dict::Dict{Symbol, Vector{Int}} - first::Int - last::Int + first::Union{Nothing, Int} + last::Union{Nothing, Int} init::Union{Nothing, Function} run_timestep::Union{Nothing, Function} @@ -434,7 +442,7 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com # All CompositeComponentInstances use a standard method that just loops over inner components. # TBD: use FunctionWrapper here? function get_func(name) - func_name = Symbol("$(name)_$(comp_name)") + func_name = Symbol("$(name)_$(self.comp_name)") try Base.eval(comp_module, func_name) catch err From f0e6df2fcab9ce8d3ce86dd66fe0d659b4adfa02 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 15 Nov 2018 17:40:55 -0800 Subject: [PATCH 14/81] WIP commit --- src/core/build.jl | 188 +++++++++++++------------ src/core/connections.jl | 21 +-- src/core/defcomp.jl | 2 +- src/core/defcomposite.jl | 7 +- src/core/defs.jl | 166 +++++++++++++--------- src/core/instances.jl | 86 ++++++------ src/core/model.jl | 9 +- src/core/types.jl | 193 ++++++++++++++------------ src/utils/graph.jl | 8 +- test/test_composite.jl | 23 ++- test/test_variables_model_instance.jl | 8 +- 11 files changed, 389 insertions(+), 322 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 44d1508a8..963c45861 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -57,12 +57,12 @@ function _instantiate_datum(md::ModelDef, def::DatumDef) end """ - _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) + _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) -Instantiate a component `comp_def` in the model `md` and its variables (but not its parameters). -Return the resulting ComponentInstanceVariables. +Instantiate a component `comp_def` in the model `md` and its variables (but not its +parameters). Return the resulting ComponentInstanceVariables. """ -function _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) +function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) comp_name = name(comp_def) var_defs = variables(comp_def) @@ -73,6 +73,47 @@ function _instantiate_component_vars(md::ModelDef, comp_def::LeafComponentDef) return ComponentInstanceVariables(names, types, values) end +function _combine_exported_vars(md::ModelDef, comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) + # exports is Vector{Pair{DatumReference, Symbol}} + names = [] + values = [] + + for (dr, name) in comp_def.subcomps.exports + if has_variable(comp_def, name) + civ = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? + value = getproperty(civ, dr.datum_name) + push!(names, name) + push!(values, value) + end + end + + types = map(typeof, values) + @info "names: $names" + @info "types: $types" + @info "values: $values" + + return ComponentInstanceVariables(Tuple(names), Tuple{types...}, Tuple(values)) +end + +# + +# Recursively instantiate all variables and store refs in the given dict. +function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}) + comp_name = name(comp_def) + + if is_composite(comp_def) + for cd in compdefs(comp_def) + _instantiate_vars(md, cd, var_dict) + end + + # Create aggregate comp-inst-vars without allocating new storage + # var_dict[comp_name] = + + else + var_dict[comp_name] = _instantiate_component_vars(md, comp_def) + end +end + # Save a reference to the model's dimension dictionary to make it # available in calls to run_timestep. function save_dim_dict_reference(mi::ModelInstance) @@ -85,30 +126,60 @@ function save_dim_dict_reference(mi::ModelInstance) return nothing end -# Return a built CompositeComponentInstance by recursively building -# all sub-components. -function build(ccd::CompositeComponentDef) - comps = Vector{T <: AbstractComponentInstance} +# Return a built leaf or composite ComponentInstance +function build(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + @info "build $(comp_def.comp_id)" + comp_name = name(comp_def) - for cd in compdefs(ccd) - ci = build(cd) - push!(comps, ci) - end + par_dict[comp_name] = par_values = Dict() # param value keyed by param name - # TBD: using ccd.exports, create the vars and params lists for the composite - vars = [] - pars = [] + subcomps = nothing - cci = CompositeComponentInstance{typeof(vars), typeof(pars)}(ccd, vars, pars, name(ccd)) - return cci -end + # recursive build... + if is_composite(comp_def) + comps = [build(md, cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] + subcomps = SubcompsInstance(comps) + + # Iterate over connections to create parameters, referencing storage in vars + for ipc in internal_param_conns(comp_def) + src_comp_name = ipc.src_comp_name + + vars = var_dict[src_comp_name] + var_value_obj = get_property_obj(vars, ipc.src_var_name) + + _par_values = par_dict[ipc.dst_comp_name] + _par_values[ipc.dst_par_name] = var_value_obj + end + + for ext in external_param_conns(comp_def) + param = external_param(comp_def, ext.external_param) + _par_values = par_dict[ext.comp_name] + _par_values[ext.param_name] = param isa ScalarModelParameter ? param : value(param) + end + + # Make the external parameter connections for the hidden ConnectorComps. + # Connect each :input2 to its associated backup value. + for (i, backup) in enumerate(backups(md)) + conn_comp_name = connector_comp_name(i) + param = external_param(comp_def, backup) + + _par_values = par_dict[conn_comp_name] + _par_values[:input2] = param isa ScalarModelParameter ? param : value(param) + end + end -# Return a built LeafComponentInstance -function build(lcd::LeafComponentDef) - vars = [] - pars = [] - lci = LeafComponentInstance{typeof(vars), typeof(pars)}(lcd, vars, pars, name(comp_def); is_composite=false) - return lci + # Do the rest for both leaf and composite components + pnames = Tuple(parameter_names(comp_def)) + pvals = [par_values[pname] for pname in pnames] + ptypes = Tuple{map(typeof, pvals)...} + pars = ComponentInstanceParameters(pnames, ptypes, pvals) + + # first = first_period(md, comp_def) + # last = last_period(md, comp_def) + # ci = ComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) + ci = ComponentInstance{typeof(subcomps), typeof(vars), typeof(pars)}(comp_def, vars, pars, comp_name, + subcomps=subcomps) + return ci end function build(m::Model) @@ -117,7 +188,6 @@ function build(m::Model) return nothing end -# TBD: this functionality needs to move to the build(ccd) and build(lcd) functions above function build(md::ModelDef) add_connector_comps(md) @@ -132,72 +202,10 @@ function build(md::ModelDef) var_dict = Dict{Symbol, Any}() # collect all var defs and par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go - comp_defs = compdefs(md) - for comp_def in comp_defs - comp_name = name(comp_def) - var_dict[comp_name] = _instantiate_component_vars(md, comp_def) - par_dict[comp_name] = Dict() # param value keyed by param name - end - - # Iterate over connections to create parameters, referencing storage in vars - for ipc in internal_param_conns(md) - comp_name = ipc.src_comp_name - - vars = var_dict[comp_name] - var_value_obj = get_property_obj(vars, ipc.src_var_name) - - par_values = par_dict[ipc.dst_comp_name] - par_values[ipc.dst_par_name] = var_value_obj - end - - for ext in external_param_conns(md) - comp_name = ext.comp_name - param = external_param(md, ext.external_param) - par_values = par_dict[comp_name] - par_values[ext.param_name] = param isa ScalarModelParameter ? param : value(param) - end - - # Make the external parameter connections for the hidden ConnectorComps. - # Connect each :input2 to its associated backup value. - for (i, backup) in enumerate(backups(md)) - comp_name = connector_comp_name(i) - param = external_param(md, backup) - - par_values = par_dict[comp_name] - par_values[:input2] = param isa ScalarModelParameter ? param : value(param) - end - - mi = ModelInstance(md) - - # Create a vector of ci instances in this following loop, then generate a - # CompositeComponentInstance from the vector. - comps = Vector{T <: AbstractComponentInstance} - - # instantiate parameters - for comp_def in comp_defs - comp_name = name(comp_def) - - vars = var_dict[comp_name] - - par_values = par_dict[comp_name] - pnames = Tuple(parameter_names(comp_def)) - pvals = [par_values[pname] for pname in pnames] - ptypes = Tuple{map(typeof, pvals)...} - pars = ComponentInstanceParameters(pnames, ptypes, pvals) - - # first = first_period(md, comp_def) - # last = last_period(md, comp_def) - - # ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) - ci = LeafComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, comp_name) - - push!(comps, ci) - # add_comp!(mi, ci) - end - - for ci{TV, TP} in comps - end + _instantiate_vars(md, md.ccd, var_dict) + ci = build(md, md.ccd, var_dict, par_dict) + mi = ModelInstance(md, ci) save_dim_dict_reference(mi) return mi end diff --git a/src/core/connections.jl b/src/core/connections.jl index 2e8092797..b089f14d6 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -19,7 +19,7 @@ function verify_units(one::AbstractString, two::AbstractString) return one == two end -function _check_labels(md::ModelDef, comp_def::AbstractComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) +function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) param_def = parameter(comp_def, param_name) t1 = eltype(ext_param.values) @@ -255,17 +255,19 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T nothing end -@delegate external_param_conns(md::ModelDef) => ccd -external_param_conns(ccd::CompositeComponentDef) = ccd.external_param_conns - +internal_param_conns(subcomps::SubcompsDef) = subcomps.internal_param_conns +@delegate internal_param_conns(ccd::CompositeComponentDef) => subcomps @delegate internal_param_conns(md::ModelDef) => ccd -internal_param_conns(ccd::CompositeComponentDef) = ccd.internal_param_conns # Find internal param conns to a given destination component function internal_param_conns(md::ModelDef, dst_comp_name::Symbol) return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(md)) end +external_param_conns(subcomps::SubcompsDef) = subcomps.external_param_conns +@delegate external_param_conns(ccd::CompositeComponentDef) => subcomps +@delegate external_param_conns(md::ModelDef) => ccd + # Find external param conns for a given comp function external_param_conns(md::ModelDef, comp_name::Symbol) return filter(x -> x.comp_name == comp_name, external_param_conns(md)) @@ -275,7 +277,7 @@ end function external_param(ccd::CompositeComponentDef, name::Symbol) try - return ccd.external_params[name] + return ccd.subcomps.external_params[name] catch err if err isa KeyError error("$name not found in external parameter list") @@ -288,19 +290,19 @@ end @delegate add_internal_param_conn!(md::ModelDef, conn::InternalParameterConnection) => ccd function add_internal_param_conn!(ccd::CompositeComponentDef, conn::InternalParameterConnection) - push!(ccd.internal_param_conns, conn) + push!(ccd.subcomps.internal_param_conns, conn) end @delegate add_external_param_conn!(md::ModelDef, conn::ExternalParameterConnection) => ccd function add_external_param_conn!(ccd::CompositeComponentDef, conn::ExternalParameterConnection) - push!(ccd.external_param_conns, conn) + push!(ccd.subcomps.external_param_conns, conn) end @delegate set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter) => ccd function set_external_param!(ccd::CompositeComponentDef, name::Symbol, value::ModelParameter) - ccd.external_params[name] = value + ccd.subcomps.external_params[name] = value end function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) @@ -476,7 +478,6 @@ function update_params!(md::ModelDef, parameters::Dict; update_timesteps = false nothing end - function add_connector_comps(md::ModelDef) conns = internal_param_conns(md) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 351b6a929..258b8ac4c 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -120,7 +120,7 @@ types of expressions are supported: 5. `run_timestep(p, v, d, t)` # defines a run_timestep function for the component Parses a @defcomp definition, converting it into a series of function calls that -create the corresponding LeafComponentDef instance. At model build time, the ModelDef +create the corresponding ComponentDef instance. At model build time, the ModelDef (including its ComponentDefs) will be converted to a runnable model. """ macro defcomp(comp_name, ex) diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 9089316fb..50ae3f97c 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -71,7 +71,7 @@ is expressed as the component id (which may be prefixed by a module, e.g., `Mimi followed by a `.` and the variable name in that component. So the form is either `modname.compname.varname` or `compname.varname`, which must be known in the current module. -Unlike LeafComponents, CompositeComponents do not have user-defined `init` or `run_timestep` +Unlike leaf components, composite components do not have user-defined `init` or `run_timestep` functions; these are defined internally to iterate over constituent components and call the associated method on each. """ @@ -145,7 +145,10 @@ macro defcomposite(cc_name, ex) # name = (alias === nothing ? comp_name : alias) # expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) - expr = :(CompositeComponentDef($comp_id, $comp_name, $comps, bindings=$bindings, exports=$exports)) + expr = :(info = SubcompsDef($comps, bindings=$bindings, exports=$exports)) + addexpr(expr) + + expr = :(ComponentDef($comp_id, $comp_name; component_info=info)) addexpr(expr) end end diff --git a/src/core/defs.jl b/src/core/defs.jl index 250c8fe08..674d7564e 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,5 +1,5 @@ # Global component registry: @defcomp stores component definitions here -global const _compdefs = Dict{ComponentId, AbstractComponentDef}() +global const _compdefs = Dict{ComponentId, ComponentDef}() compdefs() = collect(values(_compdefs)) @@ -19,10 +19,15 @@ function compdef(comp_name::Symbol) end # TBD: Might need an option like `deep=True` to recursively descend through composites -compdefs(ccd::CompositeComponentDef) = values(ccd.comps_dict) -compkeys(ccd::CompositeComponentDef) = keys(ccd.comps_dict) -hascomp(ccd::CompositeComponentDef, comp_name::Symbol) = haskey(ccd.comps_dict, comp_name) -compdef(ccd::CompositeComponentDef, comp_name::Symbol) = ccd.comps_dict[comp_name] +compdefs(subcomps::SubcompsDef) = values(subcomps.comps_dict) +compkeys(subcomps::SubcompsDef) = keys(subcomps.comps_dict) +hascomp(subcomps::SubcompsDef, comp_name::Symbol) = haskey(subcomps.comps_dict, comp_name) +compdef(subcomps::SubcompsDef, comp_name::Symbol) = subcomps.comps_dict[comp_name] + +@delegate compdefs(c::CompositeComponentDef) => subcomps +@delegate compkeys(c::CompositeComponentDef) => subcomps +@delegate hascomp(c::CompositeComponentDef, comp_name::Symbol) => subcomps +@delegate compdef(c::CompositeComponentDef, comp_name::Symbol) => subcomps @delegate compdefs(md::ModelDef) => ccd @delegate compkeys(md::ModelDef) => ccd @@ -34,7 +39,7 @@ function reset_compdefs(reload_builtins=true) empty!(_compdefs) if reload_builtins - compdir = joinpath(dirname(@__FILE__), "..", "components") + compdir = joinpath(@__DIR__, "..", "components") load_comps(compdir) end end @@ -42,9 +47,11 @@ end first_period(comp_def::LeafComponentDef) = comp_def.first last_period(comp_def::LeafComponentDef) = comp_def.last -function first_period(ccd::CompositeComponentDef) - if length(ccd.comps_dict) > 0 - firsts = [first_period(cd) for cd in compdefs(ccd)] +function first_period(comp_def::CompositeComponentDef) + subcomps = compdefs(comp_def) + + if numcomponents(subcomps) > 0 + firsts = [first_period(c) for c in subcomps] if findfirst(isequal(nothing), firsts) == nothing # i.e., there are no `nothing`s return min(Vector{Int}(firsts)...) end @@ -52,9 +59,11 @@ function first_period(ccd::CompositeComponentDef) nothing # use model's first period end -function last_period(ccd::CompositeComponentDef) - if length(ccd.comps_dict) > 0 - lasts = [last_period(cd) for cd in compdefs(ccd)] +function last_period(comp_def::CompositeComponentDef) + subcomps = compdefs(comp_def) + + if numcomponents(subcomps) > 0 + lasts = [last_period(cd) for cd in subcomps] if findfirst(isequal(nothing), lasts) == nothing # i.e., there are no `nothing`s return max(Vector{Int}(lasts)...) end @@ -62,8 +71,15 @@ function last_period(ccd::CompositeComponentDef) nothing # use model's last period end -first_period(md::ModelDef, comp_def::AbstractComponentDef) = first_period(comp_def) === nothing ? time_labels(md)[1] : first_period(comp_def) -last_period(md::ModelDef, comp_def::AbstractComponentDef) = last_period(comp_def) === nothing ? time_labels(md)[end] : last_period(comp_def) +function first_period(md::ModelDef, comp_def::ComponentDef) + period = first_period(comp_def) + return period === nothing ? time_labels(md)[1] : period +end + +function last_period(md::ModelDef, comp_def::ComponentDef) + period = last_period(comp_def) + return period === nothing ? time_labels(md)[end] : period +end # Return the module object for the component was defined in compmodule(comp_id::ComponentId) = comp_id.module_name @@ -86,7 +102,9 @@ number_type(md::ModelDef) = md.number_type @delegate numcomponents(md::ModelDef) => ccd -numcomponents(ccd::CompositeComponentDef) = length(ccd.comps_dict) +numcomponents(comp_def::LeafComponentDef) = 0 # no subcomponents +numcomponents(comp_def::CompositeComponentDef) = numcomponents(comp_def.subcomps) +numcomponents(info::SubcompsDef) = length(info.comps_dict) function dump_components() for comp in compdefs() @@ -146,7 +164,6 @@ end # Dimensions # -# TBD: is this needed for composites too? function add_dimension!(comp::LeafComponentDef, name) comp.dimensions[name] = dim_def = DimensionDef(name) return dim_def @@ -163,12 +180,13 @@ function dimensions(ccd::CompositeComponentDef) end # use Set to eliminate duplicates + # TBD: what about ordering? return collect(Set(dims)) end dimensions(def::DatumDef) = def.dimensions -# TBD: make this work for AbstractComponentDef? +# TBD: handle CompositeComponentDef dimensions(comp_def::LeafComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) dim_count(def::DatumDef) = length(dimensions(def)) @@ -322,7 +340,8 @@ end # @delegate external_params(md::ModelDef) => ccd -external_params(ccd::CompositeComponentDef) = ccd.external_params +@delegate external_params(ccd::CompositeComponentDef) => subcomps +external_params(subcomps::SubcompsDef) = subcomps.external_params function addparameter(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit, default) p = DatumDef(name, datatype, dimensions, description, unit, :parameter, default) @@ -342,13 +361,23 @@ Return a list of the parameter definitions for `comp_def`. parameters(comp_def::LeafComponentDef) = values(comp_def.parameters) function parameters(ccd::CompositeComponentDef) - params = Vector{DatumDef}() - for cd in values(ccd.comps_dict) - append!(params, parameters(cd)) + pars = ccd.parameters + + # return cached parameters, if any + if length(pars) == 0 + for (dr, name) in ccd.subcomps.exports + cd = compdef(dr.comp_id) + if has_parameter(cd, dr.datum_name) + pars[name] = parameter(cd, dr.datum_name) + end + end end - return params + + return values(pars) end +@delegate parameters(md::ModelDef) => ccd + """ parameters(comp_id::ComponentId) @@ -365,29 +394,28 @@ Return a list of all parameter names for a given component `comp_name` in a mode """ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) -parameter_names(comp_def::AbstractComponentDef) = [name(param) for param in parameters(comp_def)] +parameter_names(comp_def::ComponentDef) = [name(param) for param in parameters(comp_def)] parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) -parameter(dr::DatumReference, name::Symbol) = parameter(compdef(dr.comp_id), name) +@delegate parameter(md::ModelDef, param_name::Symbol) => ccd -function parameter(comp_def::LeafComponentDef, name::Symbol) - try - return comp_def.parameters[name] - catch - error("Parameter $name was not found in component $(comp_def.name)") +parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), dr.datum_name) + +function parameter(cd::ComponentDef, name::Symbol) + if is_composite(cd) + parameters(cd) # make sure values have been gathered end -end -# TBD: should this find the parameter regardless of whether it's exported? -function parameter(ccd::CompositeComponentDef, name::Symbol) try - return ccd.external_params[name] + return cd.parameters[name] catch - error("Parameter $name was not found in component $(ccd.name)") + error("Parameter $name was not found in component $(cd.name)") end end +has_parameter(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.parameters, name) + function parameter_unit(md::ModelDef, comp_name::Symbol, param_name::Symbol) param = parameter(md, comp_name, param_name) return param.unit @@ -471,18 +499,32 @@ end variables(comp_def::LeafComponentDef) = values(comp_def.variables) function variables(ccd::CompositeComponentDef) - vars = Vector{DatumDef}() - for cd in values(ccd.comps_dict) - append!(vars, variables(cd)) + vars = ccd.variables + + # return cached variables, if any + if length(vars) == 0 + for (dr, name) in ccd.subcomps.exports + cd = compdef(dr.comp_id) + if has_variable(cd, dr.datum_name) + vars[name] = variable(cd, dr.datum_name) + end + end end - return vars + + return values(vars) end +@delegate variables(md::ModelDef) => ccd + variables(comp_id::ComponentId) = variables(compdef(comp_id)) variables(dr::DatumReference) = variables(dr.comp_id) -function variable(comp_def::LeafComponentDef, var_name::Symbol) +function variable(comp_def::ComponentDef, var_name::Symbol) + if is_composite(comp_def) + variables(comp_def) # make sure values have been gathered + end + try return comp_def.variables[var_name] catch @@ -494,7 +536,9 @@ variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), va variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(md, comp_name), var_name) -variable(dr::DatumReference, name::Symbol) = variable(compdef(dr.comp_id), name) +variable(dr::DatumReference) = variable(compdef(dr.comp_id), dr.datum_name) + +has_variable(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.variables, name) """ variable_names(md::ModelDef, comp_name::Symbol) @@ -503,7 +547,7 @@ Return a list of all variable names for a given component `comp_name` in a model """ variable_names(md::ModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) -variable_names(comp_def::AbstractComponentDef) = [name(var) for var in variables(comp_def)] +variable_names(comp_def::ComponentDef) = [name(var) for var in variables(comp_def)] function variable_unit(md::ModelDef, comp_name::Symbol, var_name::Symbol) @@ -516,34 +560,27 @@ function variable_dimensions(md::ModelDef, comp_name::Symbol, var_name::Symbol) return var.dimensions end -# Add a variable to a LeafComponentDef +# Add a variable to a LeafComponentDef. CompositeComponents have no vars of their own, +# only references to vars in components contained within. function addvariable(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit) var_def = DatumDef(name, datatype, dimensions, description, unit, :variable) comp_def.variables[name] = var_def return var_def end -# CompositeComponents have no vars of their own, only references to vars in -# components contained within. -function addvariable(comp_def::CompositeComponentDef, var_def::DatumDef, name::Symbol) - comp_def.variables[name] = var_def - return var_def -end - """ addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) Add all exported variables to a CompositeComponentDef. """ function addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) + # TBD: this needs attention for (dr, exp_name) in exports - #comp_def.variables[name] = var_def - addvariable(comp_def, variable(comp_def, name(variable)), exp_name) end end -# Add a variable to a LeafComponentDef referenced by ComponentId +# Add a variable to a ComponentDef referenced by ComponentId function addvariable(comp_id::ComponentId, name, datatype, dimensions, description, unit) addvariable(compdef(comp_id), name, datatype, dimensions, description, unit) end @@ -558,7 +595,7 @@ function getspan(md::ModelDef, comp_name::Symbol) return getspan(md, comp_def) end -function getspan(md::ModelDef, comp_def::AbstractComponentDef) +function getspan(md::ModelDef, comp_def::ComponentDef) first = first_period(md, comp_def) last = last_period(md, comp_def) times = time_labels(md) @@ -579,18 +616,21 @@ end const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} -function _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::AbstractComponentDef) - md.ccd.comps_dict[comp_name] = comp_def +@delegate _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::ComponentDef) => ccd +@delegate _append_comp!(ccd::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) => subcomps + +function _append_comp!(subcomps::SubcompsDef, comp_name::Symbol, comp_def::ComponentDef) + subcomps.comps_dict[comp_name] = comp_def end """ - add_comp!(md::ModelDef, comp_def::LeafComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) + add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the model indcated by `md`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; +function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -630,7 +670,7 @@ function add_comp!(md::ModelDef, comp_def::LeafComponentDef, comp_name::Symbol; if before === nothing && after === nothing _append_comp!(md, comp_name, comp_def) # just add it to the end else - new_comps = OrderedDict{Symbol, AbstractComponentDef}() + new_comps = OrderedDict{Symbol, ComponentDef}() if before !== nothing if ! hascomp(md, before) @@ -701,9 +741,9 @@ unless the keywords `first` or `last` are specified. Optional boolean argument connections should be maintained in the new component. """ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing, - reconnect::Bool=true) + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing, + reconnect::Bool=true) if ! hascomp(md, comp_name) error("Cannot replace '$comp_name'; component not found in model.") @@ -791,11 +831,11 @@ end # """ - copy_comp_def(comp_def::AbstractComponentDef, comp_name::Symbol) + copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) Copy the given `comp_def`, naming the copy `comp_name`. """ -function copy_comp_def(comp_def::AbstractComponentDef, comp_name::Symbol) +function copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) obj = deepcopy(comp_def) obj.name = comp_name return obj diff --git a/src/core/instances.jl b/src/core/instances.jl index 997f3a255..fd25f56ce 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -16,24 +16,16 @@ compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) @delegate compdef(cci::CompositeComponentInstance) => leaf """ - name(ci::AbstractComponentInstance) + name(ci::ComponentInstance) Return the name of the component `ci`. """ -name(lci::LeafComponentInstance) = lci.comp_name -@delegate name(ci::CompositeComponentInstance) => leaf +name(ci::ComponentInstance) = ci.comp_name -compid(ci::LeafComponentInstance) = ci.comp_id -@delegate compid(ci::CompositeComponentInstance) => leaf - -dims(ci::LeafComponentInstance) = ci.dim_dict -@delegate dims(ci::CompositeComponentInstance) => leaf - -first_period(ci::LeafComponentInstance) = ci.first -@delegate first_period(ci::CompositeComponentInstance) => leaf - -last_period(ci::LeafComponentInstance) = ci.last -@delegate last_period(ci::CompositeComponentInstance) => leaf +compid(ci::ComponentInstance) = ci.comp_id +dims(ci::ComponentInstance) = ci.dim_dict +first_period(ci::ComponentInstance) = ci.first +last_period(ci::ComponentInstance) = ci.last """ components(mi::ModelInstance) @@ -42,27 +34,30 @@ Return an iterator over components in model instance `mi`. """ @delegate components(mi::ModelInstance) => cci +@delegate components(ci::CompositeComponentInstance) => subcomps +components(subcomps::SubcompsInstance) = values(subcomps.comp_dict) + @delegate firsts(m::Model) => cci -@delegate lasts(m::Model) => cci +@delegate lasts(m::Model) => cci @delegate clocks(m::Model) => cci -components(cci::CompositeComponentInstance) = values(cci.comp_dict) - -function add_comp!(cci::CompositeComponentInstance, ci::AbstractComponentInstance) - cci.comp_dict[name(ci)] = ci - - push!(cci.firsts, first_period(ci)) - push!(cci.lasts, last_period(ci)) -end - """ - add_comp!(mi::ModelInstance, ci::AbstractComponentInstance) + add_comp!(mi::ModelInstance, ci::ComponentInstance) Add the (leaf or composite) component `ci` to the `ModelInstance` `mi`'s list of components, and add the `first` and `last` of `mi` to the ends of the `firsts` and `lasts` lists of `mi`, respectively. """ -@delegate add_comp!(mi::ModelInstance, ci::AbstractComponentInstance) => cci +@delegate add_comp!(mi::ModelInstance, ci::ComponentInstance) => cci + +@delegate add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) => subcomps + +function add_comp!(subcomps::SubcompsInstance, ci::ComponentInstance) + subcomps.comp_dict[name(ci)] = ci + + push!(subcomps.firsts, first_period(ci)) + push!(subcomps.lasts, last_period(ci)) +end # # Setting/getting parameter and variable values @@ -114,11 +109,11 @@ end end """ - get_param_value(ci::AbstractComponentInstance, name::Symbol) + get_param_value(ci::ComponentInstance, name::Symbol) Return the value of parameter `name` in (leaf or composite) component `ci`. """ -function get_param_value(ci::LeafComponentInstance, name::Symbol) +function get_param_value(ci::ComponentInstance, name::Symbol) try return getproperty(ci.parameters, name) catch err @@ -130,14 +125,12 @@ function get_param_value(ci::LeafComponentInstance, name::Symbol) end end -@delegate get_param_value(ci::CompositeComponentInstance, name::Symbol) => leaf - """ - get_var_value(ci::AbstractComponentInstance, name::Symbol) + get_var_value(ci::ComponentInstance, name::Symbol) Return the value of variable `name` in component `ci`. """ -function get_var_value(ci::LeafComponentInstance, name::Symbol) +function get_var_value(ci::ComponentInstance, name::Symbol) try # println("Getting $name from $(ci.variables)") return getproperty(ci.variables, name) @@ -150,15 +143,9 @@ function get_var_value(ci::LeafComponentInstance, name::Symbol) end end -@delegate get_var_value(ci::CompositeComponentInstance, name::Symbol) => leaf - -set_param_value(ci::LeafComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) - -@delegate set_param_value(ci::CompositeComponentInstance, name::Symbol, value) => leaf +set_param_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) -set_var_value(ci::LeafComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) - -@delegate set_var_value(ci::CompositeComponentInstance, name::Symbol, value) => leaf +set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) # Allow values to be obtained from either parameter type using one method name. value(param::ScalarModelParameter) = param.value @@ -176,10 +163,16 @@ Return the `ComponentInstanceVariables` for `comp_name` in ModelInstance 'mi'. """ variables(mi::ModelInstance, comp_name::Symbol) = variables(compinstance(mi, comp_name)) -variables(ci::LeafComponentInstance) = ci.variables +variables(ci::ComponentInstance) = ci.variables -@delegate variables(m::Model) => cci -@delegate variables(ci::CompositeComponentInstance) => leaf +@delegate variables(mi::ModelInstance) => ci + +function variables(m::Model) + if m.mi === nothing + error("Must build model to access variables. Use variables(m.md) to get variable definitions.") + end + return variables(m.mi) +end """ parameters(mi::ModelInstance, comp_name::Symbol) @@ -189,14 +182,13 @@ Return the `ComponentInstanceParameters` for `comp_name` in ModelInstance 'mi'. parameters(mi::ModelInstance, comp_name::Symbol) = parameters(compinstance(mi, comp_name)) """ - parameters(ci::AbstractComponentInstance) + parameters(ci::ComponentInstance) Return an iterator over the parameters in `ci`. """ -parameters(ci::LeafComponentInstance) = ci.parameters +parameters(ci::ComponentInstance) = ci.parameters @delegate parameters(m::Model) => cci -@delegate parameters(ci::CompositeComponentInstance) => leaf function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) @@ -272,6 +264,8 @@ function reset_variables(cci::CompositeComponentInstance) return nothing end +@delegate reset_variables(mi::ModelInstance) => cci + function init(ci::LeafComponentInstance) reset_variables(ci) diff --git a/src/core/model.jl b/src/core/model.jl index 25c8778f9..e84ff5e8d 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -188,11 +188,11 @@ Return an iterator on the components in model `m`. @delegate getspan(m::Model, comp_name::Symbol) => md """ - datumdef(comp_def::LeafComponentDef, item::Symbol) + datumdef(comp_def::ComponentDef, item::Symbol) Return a DatumDef for `item` in the given component `comp_def`. """ -function datumdef(comp_def::LeafComponentDef, item::Symbol) +function datumdef(comp_def::ComponentDef, item::Symbol) if haskey(comp_def.variables, item) return comp_def.variables[item] @@ -203,9 +203,6 @@ function datumdef(comp_def::LeafComponentDef, item::Symbol) end end -# TBD: what to do here? Do we expose non-exported data? Have an internal LeafComponentDef that stores this stuff? -@delegate datumdef(comp_def::CompositeComponentDef, item::Symbol) => leaf - datumdef(m::Model, comp_name::Symbol, item::Symbol) = datumdef(compdef(m.md, comp_name), item) """ @@ -240,7 +237,7 @@ end @delegate parameter_unit(m::Model, comp_name::Symbol, param_name::Symbol) => md -parameter(m::Model, comp_def::AbstractComponentDef, param_name::Symbol) = parameter(comp_def, param_name) +parameter(m::Model, comp_def::ComponentDef, param_name::Symbol) = parameter(comp_def, param_name) parameter(m::Model, comp_name::Symbol, param_name::Symbol) = parameter(m, compdef(m, comp_name), param_name) diff --git a/src/core/types.jl b/src/core/types.jl index 39fb25ea7..a3dfbccae 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -241,99 +241,107 @@ struct DatumReference datum_name::Symbol end -# Supertype of LeafComponentDef and LeafComponentInstance -abstract type AbstractComponentDef <: NamedDef end +# *Def implementation doesn't need to be performance-optimized since these +# are used only to create *Instance objects that are used at run-time. With +# this in mind, we don't create dictionaries of vars, params, or dims in the +# ComponentDef since this would complicate matters if a user decides to +# add/modify/remove a component. Instead of maintaining a secondary dict, +# we just iterate over sub-components at run-time as needed. -mutable struct LeafComponentDef <: AbstractComponentDef - comp_id::ComponentId - name::Symbol +global const BindingTypes = Union{Int, Float64, DatumReference} + +# Abstract type serves as a sort of forward declaration that permits definition +# of interdependent types ComponentDef and SubcompsDef. +abstract type SubcompsDefSuper end +global const SubcompsDefTypes = Union{Nothing, SubcompsDefSuper} + +mutable struct ComponentDef{T <: SubcompsDefTypes} <: NamedDef + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) + name::Symbol # Union{Nothing, Symbol} ? variables::OrderedDict{Symbol, DatumDef} parameters::OrderedDict{Symbol, DatumDef} dimensions::OrderedDict{Symbol, DimensionDef} first::Union{Nothing, Int} last::Union{Nothing, Int} - # LeafComponentDefs are created "empty". Elements are subsequently added. - function LeafComponentDef(comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name) - self = new() + # info about sub-components, or nothing + subcomps::T + + # ComponentDefs are created "empty". Elements are subsequently added. + function ComponentDef{T}(comp_id::Union{Nothing, ComponentId}, + comp_name::Symbol=comp_id.comp_name, + subcomps::T=nothing) where {T <: SubcompsDefTypes} + self = new{T}() + + if (subcomps === nothing && comp_id === nothing) + error("Leaf ComponentDef instances must have a Symbol name (not nothing)") + end + self.comp_id = comp_id self.name = comp_name self.variables = OrderedDict{Symbol, DatumDef}() self.parameters = OrderedDict{Symbol, DatumDef}() self.dimensions = OrderedDict{Symbol, DimensionDef}() self.first = self.last = nothing + self.subcomps = subcomps return self end -end -# *Def implementation doesn't need to be performance-optimized since these -# are used only to create *Instance objects that are used at run-time. With -# this in mind, we don't create dictionaries of vars, params, or dims in the -# CompositeComponentDef since this would complicate matters if a user decides -# to add/modify/remove a component. Instead of maintaining a secondary dict, -# we just iterate over sub-components at run-time as needed. + # Syntactic sugar so caller doesn't have to specify {SubcompsDef} + function ComponentDef(comp_id::Union{Nothing, ComponentId}, name::Symbol, subcomps::T) where {T <: SubcompsDefSuper} + ComponentDef{T}(comp_id, name, subcomps) + end -global const BindingTypes = Union{Int, Float64, DatumReference} + ComponentDef() = ComponentDef(nothing, gensym("anonymous"), SubcompsDef()) +end -mutable struct CompositeComponentDef <: AbstractComponentDef - leaf::LeafComponentDef # a leaf component that simulates a single component for this composite - comp_id::Union{Nothing, ComponentId} # allow anonynous top-level CompositeComponentDefs (must be referenced by a ModelDef) - name::Union{Nothing, Symbol} - comps_dict::OrderedDict{Symbol, AbstractComponentDef} +mutable struct SubcompsDef <: SubcompsDefSuper + comps_dict::OrderedDict{Symbol, ComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} exports::Vector{Pair{DatumReference, Symbol}} - + internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} # Names of external params that the ConnectorComps will use as their :input2 parameters. backups::Vector{Symbol} - external_params::Dict{Symbol, ModelParameter} - sorted_comps::Union{Nothing, Vector{Symbol}} - function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}, - comp_name::Union{Nothing, Symbol}, - comps::Vector{<:AbstractComponentDef}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) - internal_param_conns = Vector{InternalParameterConnection}() - external_param_conns = Vector{ExternalParameterConnection}() - external_params = Dict{Symbol, ModelParameter}() - backups = Vector{Symbol}() - sorted_comps = nothing - - comps_dict = OrderedDict{Symbol, AbstractComponentDef}([name(cd) => cd for cd in comps]) - - self = new(comp_id, comp_name, comps_dict, bindings, exports, - internal_param_conns, external_param_conns, - backups, external_params, sorted_comps) - - # for (dr, exp_name) in exports - # #comp_def.variables[name] = var_def - # addvariable(comp_def, variable(comp_def, name(variable)), exp_name) - # end - + function SubcompsDef(comps::Vector{ComponentDef}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) + self = new() + self.comps_dict = OrderedDict{Symbol, ComponentDef}([name(cd) => cd for cd in comps]) + self.bindings = bindings + self.exports = exports + self.internal_param_conns = Vector{InternalParameterConnection}() + self.external_param_conns = Vector{ExternalParameterConnection}() + self.external_params = Dict{Symbol, ModelParameter}() + self.backups = Vector{Symbol}() + self.sorted_comps = nothing + return self - end + end - function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}, - comp_name::Union{Nothing, Symbol}=comp_id.comp_name) - comps = Vector{AbstractComponentDef}() + function SubcompsDef() + comps = Vector{ComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() - return CompositeComponentDef(comp_id, comp_name, comps, bindings, exports) - end - - function CompositeComponentDef() - # Create an anonymous CompositeComponentDef that must be referenced by a ModelDef - return CompositeComponentDef(nothing, nothing) + return SubcompsDef(comps, bindings, exports) end end +global const LeafComponentDef = ComponentDef{Nothing} +global const CompositeComponentDef = ComponentDef{SubcompsDef} + +is_leaf(comp::LeafComponentDef) = true +is_leaf(comp::CompositeComponentDef) = false +is_composite(comp::ComponentDef) = !is_leaf(comp) + mutable struct ModelDef - ccd::CompositeComponentDef + ccd::ComponentDef dimensions::Dict{Symbol, Dimension} number_type::DataType is_uniform::Bool @@ -345,8 +353,8 @@ mutable struct ModelDef end function ModelDef(number_type::DataType=Float64) - ccd = CompositeComponentDef() # anonymous top-level CompositeComponentDef - return ModelDef(ccd, number_type) + # passes an anonymous top-level (composite) ComponentDef + return ModelDef(ComponentDef(), number_type) end end @@ -404,15 +412,15 @@ end return getfield(dimdict, :dict)[property] end -# TBD: try with out where clause, i.e., just obj::ComponentInstanceData nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters -abstract type AbstractComponentInstance end +abstract type SubcompsInstanceSuper end +const SubcompsInstanceTypes = Union{Nothing, SubcompsInstanceSuper} -mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: AbstractComponentInstance +mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} comp_name::Symbol comp_id::ComponentId variables::TV @@ -423,11 +431,14 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com init::Union{Nothing, Function} run_timestep::Union{Nothing, Function} - function LeafComponentInstance{TV, TP}(comp_def::LeafComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def); - is_composite::Bool=false) where {TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} - self = new{TV, TP}() + # info about sub-components, or nothing + subcomps::T + + function ComponentInstance{T, TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def); subcomps::T) where + {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = new{T, TV, TP}() self.comp_id = comp_id = comp_def.comp_id self.comp_name = name self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage @@ -435,11 +446,12 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com self.parameters = pars self.first = comp_def.first self.last = comp_def.last + self.subcomps = subcomps comp_module = Base.eval(Main, comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) - # All CompositeComponentInstances use a standard method that just loops over inner components. + # All ComponentInstances use a standard method that just loops over inner components. # TBD: use FunctionWrapper here? function get_func(name) func_name = Symbol("$(name)_$(self.comp_name)") @@ -450,46 +462,49 @@ mutable struct LeafComponentInstance{TV <: ComponentInstanceVariables, TP <: Com end end - # `is_composite` indicates a LeafComponentInstance used to store summary - # data for CompositeComponentInstance and is not itself runnable. - self.run_timestep = is_composite ? nothing : get_func("run_timestep") - self.init = is_composite ? nothing : get_func("init") + # `is_composite` indicates a ComponentInstance used to store summary + # data for ComponentInstance and is not itself runnable. + self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") + self.init = is_composite(self) ? nothing : get_func("init") return self end end -mutable struct CompositeComponentInstance{TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} <: AbstractComponentInstance - - # TV, TP, and dim_dict are computed by aggregating all the vars and params from the CompositeComponent's - # sub-components. We use a LeafComponentInstance to holds all the "summary" values and references. - leaf::LeafComponentInstance{TV, TP} - comp_dict::OrderedDict{Symbol, <: AbstractComponentInstance} +mutable struct SubcompsInstance <: SubcompsInstanceSuper + comp_dict::OrderedDict{Symbol, ComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Vector{Clock} - function CompositeComponentInstance{TV, TP}( - comp_def::CompositeComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - leaf = LeafComponentInstance{TV, TP}(comp_def, vars, pars, name, true) - comps_dict = OrderedDict{Symbol, <: AbstractComponentInstance}() + function SubcompsInstance(comps::Vector{ComponentInstance}) + self = new() + comps_dict = OrderedDict{Symbol, ComponentInstance}([ci.comp_name => ci for ci in comps]) firsts = Vector{Int}() lasts = Vector{Int}() clocks = Vector{Clock}() - return new{TV, TP}(leaf, comp_dict, firsts, lasts, clocks) + return new(comp_dict, firsts, lasts, clocks) end end +const LeafComponentInstance{TV, TP} = ComponentInstance{Nothing, TV, TP} where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + +const CompositeComponentInstance{TV, TP} = ComponentInstance{SubcompsInstance, TV, TP} where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + +is_leaf(ci::LeafComponentInstance{TV, TP}) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} = true +is_leaf(ci::ComponentInstance) = false +is_composite(ci::ComponentInstance) = !is_leaf(ci) + + # ModelInstance holds the built model that is ready to be run mutable struct ModelInstance md::ModelDef - cci::Union{Nothing, CompositeComponentInstance} + ci::Union{Nothing, CompositeComponentInstance} - function ModelInstance(md::ModelDef) - return new(md, nothing) + function ModelInstance(md::ModelDef, ci::Union{Nothing, CompositeComponentInstance}=nothing) + return new(md, ci) end end diff --git a/src/utils/graph.jl b/src/utils/graph.jl index f68021819..f25860596 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -47,11 +47,11 @@ function _filter_connections(conns::Vector{InternalParameterConnection}, comp_na return collect(Iterators.filter(f, conns)) end -function get_connections(m::Model, ci::LeafComponentInstance, which::Symbol) - return get_connections(m, name(ci.comp), which) -end +function get_connections(m::Model, ci::ComponentInstance, which::Symbol) + if is_leaf(ci) + return get_connections(m, name(ci.comp), which) + end -function get_connections(m::Model, cci::CompositeComponentInstance, which::Symbol) conns = [] for ci in components(cci) append!(conns, get_connections(m, ci, which)) diff --git a/test/test_composite.jl b/test/test_composite.jl index 78a523276..1796307a4 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,7 +4,8 @@ using Test using Mimi import Mimi: - reset_compdefs, compdef, ComponentId, DatumReference, CompositeComponentDef, AbstractComponentDef, BindingTypes, ModelDef + reset_compdefs, compdef, ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, SubcompsDef, + build, time_labels reset_compdefs() @@ -58,16 +59,24 @@ let calling_module = @__MODULE__ DatumReference(Comp3, :var1) => :c3v1 ] - ccd = CompositeComponentDef(ccid, ccname, - Vector{AbstractComponentDef}(comps), - Vector{Pair{DatumReference, BindingTypes}}(bindings), - exports) - - MyComposite.md = ModelDef(ccd) + subcomps = SubcompsDef(Vector{ComponentDef}(comps), Vector{Pair{DatumReference, BindingTypes}}(bindings), exports) + MyComposite.md = ModelDef(ComponentDef(ccid, ccname, subcomps)) + set_dimension!(MyComposite, :time, 2005:2020) nothing end +m = MyComposite +md = m.md +ccd = md.ccd + +set_param!(m, :Comp1, :par1, zeros(length(time_labels(md)))) +connect_param!(md, :Comp2, :par1, :Comp1, :var1) +connect_param!(md, :Comp2, :par2, :Comp1, :var1) +connect_param!(md, :Comp3, :par1, :Comp2, :var1) + +build(MyComposite) + end # module nothing \ No newline at end of file diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index 3f1dd6685..c61222565 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -6,7 +6,7 @@ using Test import Mimi: reset_compdefs, variable_names, compinstance, get_var_value, get_param_value, set_param_value, set_var_value, dim_count, dim_key_dict, dim_value_dict, compdef, - AbstractComponentInstance, AbstractComponentDef, TimestepArray, ComponentInstanceParameters, + ComponentInstance, ComponentDef, TimestepArray, ComponentInstanceParameters, ComponentInstanceVariables reset_compdefs() @@ -42,10 +42,10 @@ cdef = compdef(ci) citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md -@test typeof(ci) <: AbstractComponentInstance && ci == compinstance(mi, :testcomp1) -@test typeof(cdef) <: AbstractComponentDef && cdef == compdef(ci.comp_id) +@test typeof(ci) <: ComponentInstance && ci == compinstance(mi, :testcomp1) +@test typeof(cdef) <: ComponentDef && cdef == compdef(ci.comp_id) @test name(ci) == :testcomp1 -@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: AbstractComponentInstance +@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: ComponentInstance #test convenience functions that can be called with name symbol From 759a33f3079642eaca1c5ecc42a1b098882d1662 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 16 Nov 2018 16:17:46 -0800 Subject: [PATCH 15/81] WIP - checkpoint --- src/core/build.jl | 166 ++++++++++++++++++++++++---------------- src/core/connections.jl | 2 +- src/core/defs.jl | 10 ++- src/core/instances.jl | 50 ++++++++---- src/core/types.jl | 28 ++++--- test/test_components.jl | 8 +- test/test_composite.jl | 11 +-- 7 files changed, 172 insertions(+), 103 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 963c45861..6a8a66322 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -73,44 +73,100 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) return ComponentInstanceVariables(names, types, values) end -function _combine_exported_vars(md::ModelDef, comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) - # exports is Vector{Pair{DatumReference, Symbol}} +# Create ComponentInstanceVariables for a composite component from the list of exported vars +function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) names = [] values = [] for (dr, name) in comp_def.subcomps.exports - if has_variable(comp_def, name) - civ = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? - value = getproperty(civ, dr.datum_name) + if is_variable(dr) + obj = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? + value = getproperty(obj, dr.datum_name) push!(names, name) push!(values, value) end end types = map(typeof, values) - @info "names: $names" - @info "types: $types" - @info "values: $values" - return ComponentInstanceVariables(Tuple(names), Tuple{types...}, Tuple(values)) end -# +function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + names = [] + values = [] + + for (dr, name) in comp_def.subcomps.exports + if is_parameter(dr) + obj = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? + value = getproperty(obj, dr.datum_name) + push!(names, name) + push!(values, value) + end + end + + types = map(typeof, values) + return ComponentInstanceParameters(Tuple(names), Tuple{types...}, Tuple(values)) +end + # Recursively instantiate all variables and store refs in the given dict. -function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}) +function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) comp_name = name(comp_def) + par_dict[comp_name] = Dict() + + if is_composite(comp_def) + @info "_instantiate_vars composite $comp_name" + for cd in compdefs(comp_def) + _instantiate_vars(md, cd, var_dict, par_dict) + end + var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) + @info "composite vars for $comp_name: $v " + else + var_dict[comp_name] = v = _instantiate_component_vars(md, comp_def) + @info "_instantiate_vars leaf $comp_name: $v" + end +end +# Recursively collect all parameters with connections to allocated storage for variablesa +function _collect_params(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) if is_composite(comp_def) + # depth-first search for cd in compdefs(comp_def) - _instantiate_vars(md, cd, var_dict) + _collect_params(cd, var_dict, par_dict) + end + + @info "Collecting params for $(comp_def.comp_id)" + + # Iterate over connections to create parameters, referencing storage in vars + for ipc in internal_param_conns(comp_def) + src_comp_name = ipc.src_comp_name + @info "internal conn: $src_comp_name" + + src_vars = var_dict[src_comp_name] + var_value_obj = get_property_obj(src_vars, ipc.src_var_name) + + comp_pars = par_dict[ipc.dst_comp_name] + comp_pars[ipc.dst_par_name] = var_value_obj end - # Create aggregate comp-inst-vars without allocating new storage - # var_dict[comp_name] = + for ext in external_param_conns(comp_def) + param = external_param(comp_def, ext.external_param) + @info "external conn: $(ext.comp_name)" + comp_pars = par_dict[ext.comp_name] + comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) + end - else - var_dict[comp_name] = _instantiate_component_vars(md, comp_def) + # Make the external parameter connections for the hidden ConnectorComps. + # Connect each :input2 to its associated backup value. + for (i, backup) in enumerate(backups(comp_def)) + conn_comp_name = connector_comp_name(i) + @info "backup: $conn_comp_name" + + param = external_param(comp_def, backup) + + comp_pars = par_dict[conn_comp_name] + comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) + end end end @@ -119,67 +175,44 @@ end function save_dim_dict_reference(mi::ModelInstance) dim_dict = dim_value_dict(mi.md) - for ci in values(mi.components) + for ci in components(mi) ci.dim_dict = dim_dict end return nothing end -# Return a built leaf or composite ComponentInstance -function build(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - @info "build $(comp_def.comp_id)" +function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + @info "Instantiating params for $(comp_def.comp_id)" + comp_name = name(comp_def) + d = par_dict[comp_name] - par_dict[comp_name] = par_values = Dict() # param value keyed by param name + pnames = Tuple(parameter_names(comp_def)) + pvals = [d[pname] for pname in pnames] + ptypes = Tuple{map(typeof, pvals)...} - subcomps = nothing + return ComponentInstanceParameters(pnames, ptypes, pvals) +end + +# Return a built leaf or composite ComponentInstance +function build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + @info "build $(comp_def.comp_id)" + @info "var_dict $(keys(var_dict))" + @info "par_dict $(keys(par_dict))" + comp_name = name(comp_def) # recursive build... if is_composite(comp_def) - comps = [build(md, cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] + comps = [build(cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] subcomps = SubcompsInstance(comps) - - # Iterate over connections to create parameters, referencing storage in vars - for ipc in internal_param_conns(comp_def) - src_comp_name = ipc.src_comp_name - - vars = var_dict[src_comp_name] - var_value_obj = get_property_obj(vars, ipc.src_var_name) - - _par_values = par_dict[ipc.dst_comp_name] - _par_values[ipc.dst_par_name] = var_value_obj - end - - for ext in external_param_conns(comp_def) - param = external_param(comp_def, ext.external_param) - _par_values = par_dict[ext.comp_name] - _par_values[ext.param_name] = param isa ScalarModelParameter ? param : value(param) - end - - # Make the external parameter connections for the hidden ConnectorComps. - # Connect each :input2 to its associated backup value. - for (i, backup) in enumerate(backups(md)) - conn_comp_name = connector_comp_name(i) - param = external_param(comp_def, backup) - - _par_values = par_dict[conn_comp_name] - _par_values[:input2] = param isa ScalarModelParameter ? param : value(param) - end + else + subcomps = nothing end - # Do the rest for both leaf and composite components - pnames = Tuple(parameter_names(comp_def)) - pvals = [par_values[pname] for pname in pnames] - ptypes = Tuple{map(typeof, pvals)...} - pars = ComponentInstanceParameters(pnames, ptypes, pvals) - - # first = first_period(md, comp_def) - # last = last_period(md, comp_def) - # ci = ComponentInstance{typeof(vars), typeof(pars)}(comp_def, vars, pars, first, last, comp_name) - ci = ComponentInstance{typeof(subcomps), typeof(vars), typeof(pars)}(comp_def, vars, pars, comp_name, - subcomps=subcomps) - return ci + pars = _instantiate_params(comp_def, par_dict) + vars = var_dict[comp_name] + return ComponentInstance(comp_def, vars, pars, comp_name, subcomps=subcomps) end function build(m::Model) @@ -202,9 +235,14 @@ function build(md::ModelDef) var_dict = Dict{Symbol, Any}() # collect all var defs and par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go - _instantiate_vars(md, md.ccd, var_dict) + comp_def = md.ccd + _instantiate_vars(md, comp_def, var_dict, par_dict) + _collect_params(comp_def, var_dict, par_dict) + + @info "var_dict: $var_dict" + @info "par_dict: $par_dict" - ci = build(md, md.ccd, var_dict, par_dict) + ci = build(comp_def, var_dict, par_dict) mi = ModelInstance(md, ci) save_dim_dict_reference(mi) return mi diff --git a/src/core/connections.jl b/src/core/connections.jl index b089f14d6..553e76371 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -63,7 +63,7 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, end @delegate backups(md::ModelDef) => ccd -backups(ccd::CompositeComponentDef) = ccd.backups +backups(ccd::CompositeComponentDef) = ccd.subcomps.backups @delegate add_backup!(md::ModelDef, obj) => ccd add_backup!(ccd::CompositeComponentDef, obj) = push!(ccd.backups, obj) diff --git a/src/core/defs.jl b/src/core/defs.jl index 674d7564e..48278d8c3 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -86,6 +86,13 @@ compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name +name(dr::DatumReference) = dr.datum_name +@delegate compname(dr::DatumReference) => comp_id +@delegate compmodule(dr::DatumReference) => comp_id + +is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), name(dr)) +is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), name(dr)) + function Base.show(io::IO, comp_id::ComponentId) print(io, "") end @@ -394,7 +401,8 @@ Return a list of all parameter names for a given component `comp_name` in a mode """ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) -parameter_names(comp_def::ComponentDef) = [name(param) for param in parameters(comp_def)] +#parameter_names(comp_def::ComponentDef) = [name(param) for param in parameters(comp_def)] +parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) diff --git a/src/core/instances.jl b/src/core/instances.jl index fd25f56ce..121c95a52 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -9,11 +9,16 @@ Return the `ModelDef` contained by ModelInstance `mi`. """ modeldef(mi::ModelInstance) = mi.md -compinstance(cci::CompositeComponentInstance, name::Symbol) = cci.comp_dict[name] +compinstance(SubcompsInstance, name::Symbol) = subcomps.comps_dict[name] +@delegate compinstance(cci::CompositeComponentInstance, name::Symbol) => subcomps @delegate compinstance(mi::ModelInstance, name::Symbol) => cci -compdef(lci::LeafComponentInstance) = compdef(lci.comp_id) -@delegate compdef(cci::CompositeComponentInstance) => leaf +has_component(subcomps::SubcompsInstance, name::Symbol) = haskey(subcomps.comps_dict, name) +@delegate has_component(ci::CompositeComponentInstance, name::Symbol) => subcomps +@delegate has_component(mi::ModelInstance, name::Symbol) => cci + + +compdef(ci::ComponentInstance) = compdef(ci.comp_id) """ name(ci::ComponentInstance) @@ -35,11 +40,22 @@ Return an iterator over components in model instance `mi`. @delegate components(mi::ModelInstance) => cci @delegate components(ci::CompositeComponentInstance) => subcomps -components(subcomps::SubcompsInstance) = values(subcomps.comp_dict) +components(subcomps::SubcompsInstance) = values(subcomps.comps_dict) + +firsts(subcomps::SubcompsInstance) = subcomps.firsts +@delegate firsts(cci::CompositeComponentInstance) => subcomps +@delegate firsts(mi::ModelInstance) => cci +@delegate firsts(m::Model) => mi + +lasts(subcomps::SubcompsInstance) = subcomps.lasts +@delegate lasts(cci::CompositeComponentInstance) => subcomps +@delegate lasts(mi::ModelInstance) => cci +@delegate lasts(m::Model) => mi -@delegate firsts(m::Model) => cci -@delegate lasts(m::Model) => cci -@delegate clocks(m::Model) => cci +clocks(subcomps::SubcompsInstance) = subcomps.clocks +@delegate clocks(cci::CompositeComponentInstance) => subcomps +@delegate clocks(mi::ModelInstance) => cci +@delegate clocks(m::Model) => mi """ add_comp!(mi::ModelInstance, ci::ComponentInstance) @@ -53,7 +69,7 @@ components, and add the `first` and `last` of `mi` to the ends of the `firsts` a @delegate add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) => subcomps function add_comp!(subcomps::SubcompsInstance, ci::ComponentInstance) - subcomps.comp_dict[name(ci)] = ci + subcomps.comps_dict[name(ci)] = ci push!(subcomps.firsts, first_period(ci)) push!(subcomps.lasts, last_period(ci)) @@ -192,7 +208,7 @@ parameters(ci::ComponentInstance) = ci.parameters function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) - if !(comp_name in keys(mi.components)) + if ! has_component(mi, comp_name) error("Component :$comp_name does not exist in current model") end @@ -321,23 +337,23 @@ end function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dimkeys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) - if length(mi.components) == 0 + if length(components(mi)) == 0 error("Cannot run the model: no components have been created.") end t::Vector{Int} = dimkeys === nothing ? dim_keys(mi.md, :time) : dimkeys[:time] - firsts = mi.firsts - lasts = mi.lasts + firsts_vec = firsts(mi) + lasts_vec = lasts(mi) if isuniform(t) _, stepsize = first_and_step(t) - comp_clocks = [Clock{FixedTimestep}(first, stepsize, last) for (first, last) in zip(firsts, lasts)] + comp_clocks = [Clock{FixedTimestep}(first, stepsize, last) for (first, last) in zip(firsts_vec, lasts_vec)] else - comp_clocks = Array{Clock{VariableTimestep}}(undef, length(firsts)) + comp_clocks = Array{Clock{VariableTimestep}}(undef, length(firsts_vec)) for i = 1:length(firsts) - first_index = findfirst(isequal(firsts[i]), t) - last_index = findfirst(isequal(lasts[i]), t) + first_index = findfirst(isequal(firsts_vec[i]), t) + last_index = findfirst(isequal(lasts_vec[i]), t) times = (t[first_index:last_index]...,) comp_clocks[i] = Clock{VariableTimestep}(times) end @@ -347,6 +363,6 @@ function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), init(mi) # call module's (or fallback) init function - _run_components(mi, clock, firsts, lasts, comp_clocks) + _run_components(mi, clock, firsts_vec, lasts_vec, comp_clocks) nothing end diff --git a/src/core/types.jl b/src/core/types.jl index a3dfbccae..ca0c583d2 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -435,7 +435,7 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst subcomps::T function ComponentInstance{T, TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def); subcomps::T) where + name::Symbol=name(comp_def); subcomps::T=nothing) where {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = new{T, TV, TP}() @@ -471,19 +471,25 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst end end +function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def); subcomps::T=nothing) where + {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + ComponentInstance{T, TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) +end + mutable struct SubcompsInstance <: SubcompsInstanceSuper - comp_dict::OrderedDict{Symbol, ComponentInstance} + comps_dict::OrderedDict{Symbol, ComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Vector{Clock} - function SubcompsInstance(comps::Vector{ComponentInstance}) + function SubcompsInstance(comps::Vector{T}) where {T <: ComponentInstance} self = new() - comps_dict = OrderedDict{Symbol, ComponentInstance}([ci.comp_name => ci for ci in comps]) - firsts = Vector{Int}() - lasts = Vector{Int}() - clocks = Vector{Clock}() - return new(comp_dict, firsts, lasts, clocks) + self.comps_dict = OrderedDict{Symbol, ComponentInstance}([ci.comp_name => ci for ci in comps]) + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + self.clocks = Vector{Clock}() + return self end end @@ -501,10 +507,10 @@ is_composite(ci::ComponentInstance) = !is_leaf(ci) # ModelInstance holds the built model that is ready to be run mutable struct ModelInstance md::ModelDef - ci::Union{Nothing, CompositeComponentInstance} + cci::Union{Nothing, CompositeComponentInstance} - function ModelInstance(md::ModelDef, ci::Union{Nothing, CompositeComponentInstance}=nothing) - return new(md, ci) + function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) + return new(md, cci) end end diff --git a/test/test_components.jl b/test/test_components.jl index a71a8dbcb..335bdb69b 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -130,7 +130,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model set_param!(m, :C, :par1, zeros(5)) Mimi.build(m) # Build the model -ci = m.mi.components[:C] # Get the component instance +ci = compinstance(m.mi, :C) # Get the component instance @test ci.first == 2001 # The component instance's first and last values should match the model's index @test ci.last == 2005 @@ -141,7 +141,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model update_param!(m, :par1, zeros(16); update_timesteps=true) Mimi.build(m) # Build the model -ci = m.mi.components[:C] # Get the component instance +ci = compinstance(m.mi, :C) # Get the component instance @test ci.first == 2005 # The component instance's first and last values should match the model's index @test ci.last == 2020 @@ -157,7 +157,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model set_param!(m, :C, :par1, zeros(81)) Mimi.build(m) # Build the model -ci = m.mi.components[:C] # Get the component instance +ci = compinstance(m.mi, :C) # Get the component instance @test ci.first == 2010 # The component instance's first and last values are the same as in the comp def @test ci.last == 2090 @@ -167,7 +167,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model @test cd.last == 2090 Mimi.build(m) # Build the model -ci = m.mi.components[:C] # Get the component instance +ci = compinstance(m.mi, :C) # Get the component instance @test ci.first == 2010 # The component instance's first and last values are the same as the comp def @test ci.last == 2090 diff --git a/test/test_composite.jl b/test/test_composite.jl index 1796307a4..0afef5a9a 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,8 +4,8 @@ using Test using Mimi import Mimi: - reset_compdefs, compdef, ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, SubcompsDef, - build, time_labels + reset_compdefs, compdef, ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, + SubcompsDef, SubcompsDefTypes, build, time_labels reset_compdefs() @@ -45,9 +45,10 @@ let calling_module = @__MODULE__ ccname = :testcomp ccid = ComponentId(calling_module, ccname) - comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] + comps::Vector{ComponentDef{<: SubcompsDefTypes}} = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] - bindings = [ + # TBD: need to implement this to create connections and default value + bindings::Vector{Pair{DatumReference, BindingTypes}} = [ DatumReference(Comp2, :par1) => 5, # bind Comp1.par1 to constant value of 5 DatumReference(Comp2, :par1) => DatumReference(Comp1, :var1), # connect target Comp2.par1 to source Comp1.var1 DatumReference(Comp3, :par1) => DatumReference(Comp2, :var1) @@ -59,7 +60,7 @@ let calling_module = @__MODULE__ DatumReference(Comp3, :var1) => :c3v1 ] - subcomps = SubcompsDef(Vector{ComponentDef}(comps), Vector{Pair{DatumReference, BindingTypes}}(bindings), exports) + subcomps = SubcompsDef(comps, bindings, exports) MyComposite.md = ModelDef(ComponentDef(ccid, ccname, subcomps)) set_dimension!(MyComposite, :time, 2005:2020) From cc1569e50e19bb453929a0d69a00bf9482d7ded9 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 19 Nov 2018 13:43:31 -0800 Subject: [PATCH 16/81] Modified @delegate to handle more cases (kwargs, default values, extra expression to insert) --- src/core/model.jl | 62 ++++++++++++----------------------------------- src/core/types.jl | 56 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/core/model.jl b/src/core/model.jl index e84ff5e8d..d231367ad 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -73,30 +73,19 @@ function connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, S connect_param!(m.md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end -function disconnect_param!(m::Model, comp_name::Symbol, param_name::Symbol) - disconnect_param!(m.md, comp_name, param_name) - decache(m) -end +@delegate(disconnect_param!(m::Model, comp_name::Symbol, param_name::Symbol) => md, decache(m)) -function set_external_param!(m::Model, name::Symbol, value::ModelParameter) - set_external_param!(m.md, name, value) - decache(m) -end +@delegate(set_external_param!(m::Model, name::Symbol, value::ModelParameter) => md, decache(m)) -function set_external_param!(m::Model, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) - set_external_param!(m.md, name, value; param_dims = param_dims) - decache(m) -end +@delegate(set_external_param!(m::Model, name::Symbol, value::Number; + param_dims::Union{Nothing,Array{Symbol}} = nothing) => md, decache(m)) -function set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; param_dims::Union{Nothing,Array{Symbol}} = nothing) - set_external_param!(m.md, name, value; param_dims = param_dims) -end +@delegate(set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; + param_dims::Union{Nothing,Array{Symbol}} = nothing) => md, decache(m)) -function add_internal_param_conn!(m::Model, conn::InternalParameterConnection) - add_internal_param_conn!(m.md, conn) - decache(m) -end +@delegate(add_internal_param_conn!(m::Model, conn::InternalParameterConnection) => md, decache(m)) +# @delegate doesn't handle the 'where T' currently. This is the only instance of it for now... function set_leftover_params!(m::Model, parameters::Dict{T, Any}) where T set_leftover_params!(m.md, parameters) decache(m) @@ -110,10 +99,7 @@ Update the `value` of an external model parameter in model `m`, referenced by indicates whether to update the time keys associated with the parameter values to match the model's time index. """ -function update_param!(m::Model, name::Symbol, value; update_timesteps = false) - update_param!(m.md, name, value, update_timesteps = update_timesteps) - decache(m) -end +@delegate(update_param!(m::Model, name::Symbol, value; update_timesteps = false) => md, decache(m)) """ update_params!(m::Model, parameters::Dict{T, Any}; update_timesteps = false) where T @@ -124,10 +110,7 @@ Boolean argument update_timesteps. Each key k must be a symbol or convert to a symbol matching the name of an external parameter that already exists in the model definition. """ -function update_params!(m::Model, parameters::Dict; update_timesteps = false) - update_params!(m.md, parameters; update_timesteps = update_timesteps) - decache(m) -end +@delegate(update_params!(m::Model, parameters::Dict; update_timesteps = false) => md, decache(m)) """ add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; @@ -224,10 +207,7 @@ dimensions(m::Model, comp_name::Symbol, datum_name::Symbol) = dimensions(compdef Set the values of `m` dimension `name` to integers 1 through `count`, if `keys`` is an integer; or to the values in the vector or range if `keys`` is either of those types. """ -function set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) - set_dimension!(m.md, name, keys) - decache(m) -end +@delegate(set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => md, decache(m)) @delegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md @@ -278,30 +258,21 @@ variables(m::Model, comp_name::Symbol) = variables(compdef(m, comp_name)) Add a one or two dimensional (optionally, time-indexed) array parameter `name` with value `value` to the model `m`. """ -function set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) - set_external_array_param!(m.md, name, value, dims) - decache(m) -end +@delegate(set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) => md, decache(m)) """ set_external_scalar_param!(m::Model, name::Symbol, value::Any) Add a scalar type parameter `name` with value `value` to the model `m`. """ -function set_external_scalar_param!(m::Model, name::Symbol, value::Any) - set_external_scalar_param!(m.md, name, value) - decache(m) -end +@delegate(set_external_scalar_param!(m::Model, name::Symbol, value::Any) => md, decache(m)) """ delete!(m::ModelDef, component::Symbol Delete a `component`` by name from a model `m`'s ModelDef, and nullify the ModelInstance. """ -function Base.delete!(m::Model, comp_name::Symbol) - delete!(m.md, comp_name) - decache(m) -end +@delegate(Base.delete!(m::Model, comp_name::Symbol) => md, decache(m)) """ set_param!(m::Model, comp_name::Symbol, name::Symbol, value, dims=nothing) @@ -311,10 +282,7 @@ The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ -function set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) - set_param!(m.md, comp_name, param_name, value, dims) - decache(m) -end +@delegate(set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md, decache(m)) """ run(m::Model) diff --git a/src/core/types.jl b/src/core/types.jl index ca0c583d2..950208050 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -3,8 +3,24 @@ # using MacroTools -# Convert a list of args with optional type specs to just the arg symbols -_arg_names(args::Vector) = [a isa Symbol ? a : a.args[1] for a in args] +function delegated_args(args::Vector) + newargs = [] + @info "delegated_args: $args" + for a in args + if a isa Symbol + push!(newargs, a) + + elseif (@capture(a, var_::T_ = val_) || @capture(a, var_ = val_)) + push!(newargs, :($var = $var)) + + elseif @capture(a, var_::T_) + push!(newargs, var) + else + error("Unrecognized argument format: $a") + end + end + return newargs +end """ Macro to define a method that simply delegate to a method with the same signature @@ -16,16 +32,40 @@ in the delegated call. That is, expands to: `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` + +If a second expression is given, it is spliced in (basically to support "decache(m)") """ -macro delegate(ex) +macro delegate(ex, other=nothing) + result = nothing + if @capture(ex, fname_(varname_::T_, args__) => rhs_) - argnames = _arg_names(args) - result = esc(:($fname($varname::$T, $(args...)) = $fname($varname.$rhs, $(argnames...)))) - return result + # @info "args: $args" + new_args = delegated_args(args) + result = quote + function $fname($varname::$T, $(args...)) + retval = $fname($varname.$rhs, $(new_args...)) + $other + return retval + end + end + elseif @capture(ex, fname_(varname_::T_, args__; kwargs__) => rhs_) + # @info "args: $args, kwargs: $kwargs" + new_args = delegated_args(args) + new_kwargs = delegated_args(kwargs) + result = quote + function $fname($varname::$T, $(args...); $(kwargs...)) + retval = $fname($varname.$rhs, $(new_args...); $(new_kwargs...)) + $other + return retval + end + end end - error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") -end + if result === nothing + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") + end + return esc(result) +end # # 1. Types supporting parameterized Timestep and Clock objects From 8d612f89a83576a69ec01ab7bd95c718e3bfd895 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 28 Nov 2018 13:09:51 -0800 Subject: [PATCH 17/81] WIP - composite comps --- src/Mimi.jl | 7 +- src/core/build.jl | 65 ++++----- src/core/classes.jl | 324 +++++++++++++++++++++++++++++++++++++++++ src/core/defs.jl | 55 +++++-- src/core/delegate.jl | 147 +++++++++++++++++++ src/core/instances.jl | 7 +- src/core/types.jl | 77 +--------- test/test_composite.jl | 49 ++++--- 8 files changed, 585 insertions(+), 146 deletions(-) create mode 100644 src/core/classes.jl create mode 100644 src/core/delegate.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index 970d31b98..55a981f5c 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -42,8 +42,9 @@ export variables include("core/types.jl") +include("core/delegate.jl") -# After loading types and delegation macro, the rest can just be alphabetical +# After loading types and delegation macro, the rest is alphabetical include("core/build.jl") include("core/connections.jl") include("core/defs.jl") @@ -56,11 +57,11 @@ include("core/time.jl") include("core/model.jl") include("explorer/explore.jl") include("mcs/mcs.jl") -include("utils/graph.jl") -include("utils/plotting.jl") include("utils/getdataframe.jl") +include("utils/graph.jl") include("utils/lint_helper.jl") include("utils/misc.jl") +include("utils/plotting.jl") """ load_comps(dirname::String="./components") diff --git a/src/core/build.jl b/src/core/build.jl index 6a8a66322..b25c0969c 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -97,8 +97,8 @@ function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{ for (dr, name) in comp_def.subcomps.exports if is_parameter(dr) - obj = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? - value = getproperty(obj, dr.datum_name) + d = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? + value = d[dr.datum_name] push!(names, name) push!(values, value) end @@ -139,40 +139,35 @@ function _collect_params(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, pa # Iterate over connections to create parameters, referencing storage in vars for ipc in internal_param_conns(comp_def) - src_comp_name = ipc.src_comp_name - @info "internal conn: $src_comp_name" - - src_vars = var_dict[src_comp_name] + src_vars = var_dict[ipc.src_comp_name] var_value_obj = get_property_obj(src_vars, ipc.src_var_name) - comp_pars = par_dict[ipc.dst_comp_name] comp_pars[ipc.dst_par_name] = var_value_obj + @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" end for ext in external_param_conns(comp_def) param = external_param(comp_def, ext.external_param) - @info "external conn: $(ext.comp_name)" comp_pars = par_dict[ext.comp_name] comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) + @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" end # Make the external parameter connections for the hidden ConnectorComps. # Connect each :input2 to its associated backup value. for (i, backup) in enumerate(backups(comp_def)) conn_comp_name = connector_comp_name(i) - @info "backup: $conn_comp_name" - param = external_param(comp_def, backup) - comp_pars = par_dict[conn_comp_name] comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) + @info "backup: $conn_comp_name $param" end end end # Save a reference to the model's dimension dictionary to make it # available in calls to run_timestep. -function save_dim_dict_reference(mi::ModelInstance) +function _save_dim_dict_reference(mi::ModelInstance) dim_dict = dim_value_dict(mi.md) for ci in components(mi) @@ -185,26 +180,30 @@ end function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @info "Instantiating params for $(comp_def.comp_id)" - comp_name = name(comp_def) - d = par_dict[comp_name] - - pnames = Tuple(parameter_names(comp_def)) - pvals = [d[pname] for pname in pnames] - ptypes = Tuple{map(typeof, pvals)...} + if is_composite(comp_def) + return _combine_exported_pars(comp_def, par_dict) - return ComponentInstanceParameters(pnames, ptypes, pvals) + else + comp_name = name(comp_def) + d = par_dict[comp_name] + + pnames = Tuple(parameter_names(comp_def)) + pvals = [d[pname] for pname in pnames] + ptypes = Tuple{map(typeof, pvals)...} + return ComponentInstanceParameters(pnames, ptypes, pvals) + end end # Return a built leaf or composite ComponentInstance -function build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - @info "build $(comp_def.comp_id)" - @info "var_dict $(keys(var_dict))" - @info "par_dict $(keys(par_dict))" +function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + @info "_build $(comp_def.comp_id)" + @info " var_dict $(var_dict)" + @info " par_dict $(par_dict)" comp_name = name(comp_def) # recursive build... if is_composite(comp_def) - comps = [build(cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] + comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] subcomps = SubcompsInstance(comps) else subcomps = nothing @@ -215,13 +214,7 @@ function build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Di return ComponentInstance(comp_def, vars, pars, comp_name, subcomps=subcomps) end -function build(m::Model) - # Reference a copy in the ModelInstance to avoid changes underfoot - m.mi = build(deepcopy(m.md)) - return nothing -end - -function build(md::ModelDef) +function _build(md::ModelDef) add_connector_comps(md) # check if all parameters are set @@ -242,12 +235,18 @@ function build(md::ModelDef) @info "var_dict: $var_dict" @info "par_dict: $par_dict" - ci = build(comp_def, var_dict, par_dict) + ci = _build(comp_def, var_dict, par_dict) mi = ModelInstance(md, ci) - save_dim_dict_reference(mi) + _save_dim_dict_reference(mi) return mi end +function build(m::Model) + # Reference a copy in the ModelInstance to avoid changes underfoot + m.mi = _build(deepcopy(m.md)) + return nothing +end + """ create_marginal_model(base::Model, delta::Float64=1.0) diff --git a/src/core/classes.jl b/src/core/classes.jl new file mode 100644 index 000000000..d86ac2732 --- /dev/null +++ b/src/core/classes.jl @@ -0,0 +1,324 @@ +# Objects with a `name` attribute +@class Named begin + name::Symbol +end + +@method name(obj::Named) = obj.name + +@class DimensionDef <: Named + +# Similar structure is used for variables and parameters (parameters has `default`) +@class mutable VarDef <: Named begin + datatype::DataType + dimensions::Vector{Symbol} + description::String + unit::String +end + +@class mutable ParDef <: VarDef begin + # ParDef adds a default value, which can be specified in @defcomp + default::Any +end + + +@class mutable ComponentDef <: Named begin + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) + name::Symbol # Union{Nothing, Symbol} ? + variables::OrderedDict{Symbol, VarDef} + parameters::OrderedDict{Symbol, ParDef} + dimensions::OrderedDict{Symbol, DimensionDef} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + is_uniform::Bool + + # ComponentDefs are created "empty". Elements are subsequently added. + function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) + if (is_leaf(self) && comp_id === nothing) + error("Leaf ComponentDef objects must have a Symbol name (not nothing)") + end + + self.comp_id = comp_id + self.name = comp_name + self.variables = OrderedDict{Symbol, DatumDef}() + self.parameters = OrderedDict{Symbol, DatumDef}() + self.dimensions = OrderedDict{Symbol, DimensionDef}() + self.first = self.last = nothing + self.is_uniform = true + return self + end + + function ComponentDef(comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) + return ComponentDef(new(), comp_id, comp_name) + end + + # ComponentDef() = ComponentDef(nothing, gensym("anonymous")) +end + +@class mutable CompositeDef <: ComponentDef begin + comps_dict::OrderedDict{Symbol, _ComponentDef_} + bindings::Vector{Pair{DatumReference, BindingTypes}} + exports::Vector{Pair{DatumReference, Symbol}} + + internal_param_conns::Vector{InternalParameterConnection} + external_param_conns::Vector{ExternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} + + # Names of external params that the ConnectorComps will use as their :input2 parameters. + backups::Vector{Symbol} + + sorted_comps::Union{Nothing, Vector{Symbol}} + + function CompositeDef(comps::Vector{T}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} + self = new() + + # superclass initialization + # ComponentDef(self, comp_id, comp_name) + + self.comps_dict = OrderedDict{Symbol, T}([name(cd) => cd for cd in comps]) + self.bindings = bindings + self.exports = exports + self.internal_param_conns = Vector{InternalParameterConnection}() + self.external_param_conns = Vector{ExternalParameterConnection}() + self.external_params = Dict{Symbol, ModelParameter}() + self.backups = Vector{Symbol}() + self.sorted_comps = nothing + + return self + end + + function CompositeDef() + comps = Vector{<: _ComponentDef_}() + bindings = Vector{Pair{DatumReference, BindingTypes}}() + exports = Vector{Pair{DatumReference, Symbol}}() + return CompositeDef(comps, bindings, exports) + end +end + +@method is_leaf(comp::ComponentDef) = true +@method is_leaf(comp::CompositeDef) = false +@method is_composite(comp::ComponentDef) = !is_leaf(comp) + +# TBD: Does a ModelDef contain a CompositeDef, or is it a subclass? + +@class mutable ModelDef <: CompositeDef begin + dimensions::Dict{Symbol, Dimension} # TBD: use the one in ccd instead + number_type::DataType + + # TBD: these methods assume sub-elements rather than subclasses + function ModelDef(ccd::CompositeDef, number_type::DataType=Float64) + dimensions = Dict{Symbol, Dimension}() + return new(ccd, dimensions, number_type) + end + + function ModelDef(number_type::DataType=Float64) + # passes an anonymous top-level (composite) ComponentDef + return ModelDef(ComponentDef(), number_type) + end +end + +# +# 5. Types supporting instantiated models and their components +# + +# Supertype for variables and parameters in component instances +@class InstanceData{NT <: NamedTuple} begin + nt::NT +end + +@class InstanceParameters{NT <: NamedTuple} <: InstanceData + +function InstanceParameters(names, types, values) + NT = NamedTuple{names, types} + InstanceParameters{NT}(NT(values)) +end + +function InstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + InstanceParameters{NT}(NT(values)) +end + + +@class InstanceVariables{NT <: NamedTuple} <: InstanceData + +function InstanceVariables(names, types, values) + NT = NamedTuple{names, types} + InstanceVariables{NT}(NT(values)) +end + +function InstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + InstanceVariables{NT}(NT(values)) +end + +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimDict + dict::Dict{Symbol, Vector{Int}} +end + +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimDict of dimensions by name +# as the "d" parameter. +@inline function Base.getproperty(dimdict::DimDict, property::Symbol) + return getfield(dimdict, :dict)[property] +end + +@method nt(obj::InstanceData) = getfield(obj, :nt) +@method Base.names(obj::InstanceData) = keys(nt(obj)) +@method Base.values(obj::InstanceData) = values(nt(obj)) +@method types(obj::InstanceData) = typeof(nt(obj)).parameters[2].parameters + +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin + comp_name::Symbol + comp_id::ComponentId + variables::TV + parameters::TP + dim_dict::Dict{Symbol, Vector{Int}} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + init::Union{Nothing, Function} + run_timestep::Union{Nothing, Function} + + function ComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where + {TV <: InstanceVariables, TP <: InstanceParameters} + + self = new{TV, TP}() + self.comp_id = comp_id = comp_def.comp_id + self.comp_name = name + self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.variables = vars + self.parameters = pars + self.first = comp_def.first + self.last = comp_def.last + + comp_module = Base.eval(Main, comp_id.module_name) + + # The try/catch allows components with no run_timestep function (as in some of our test cases) + # All ComponentInstances use a standard method that just loops over inner components. + # TBD: use FunctionWrapper here? + function get_func(name) + func_name = Symbol("$(name)_$(self.comp_name)") + try + Base.eval(comp_module, func_name) + catch err + nothing + end + end + + # `is_composite` indicates a ComponentInstance used to store summary + # data for ComponentInstance and is not itself runnable. + self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") + self.init = is_composite(self) ? nothing : get_func("init") + + return self + end +end + +function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) +end + +@class mutable CompositeInstance <: ComponentInstance begin + comps_dict::OrderedDict{Symbol, _ComponentInstance_} + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} + clocks::Vector{Clock} + + function CompositeInstance(comps::Vector{T}) where {T <: _ComponentInstance_} + self = new() + self.comps_dict = OrderedDict{Symbol, _ComponentInstance_}([ci.comp_name => ci for ci in comps]) + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + self.clocks = Vector{Clock}() + return self + end +end + +@method is_leaf(ci::ComponentInstance) = true +@method is_leaf(ci::CompositeInstance) = false +@method is_composite(ci::ComponentInstance) = !is_leaf(ci) + +# TBD: @class or container? +# ModelInstance holds the built model that is ready to be run +mutable struct ModelInstance <: CompositeInstance + md::ModelDef + cci::Union{Nothing, CompositeInstance} + + function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) + return new(md, cci) + end +end + +# +# 6. User-facing Model types providing a simplified API to model definitions and instances. +# +""" + Model + +A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). +This `Model` can be created with the optional keyword argument `number_type` indicating +the default type of number used for the `ModelDef`. If not specified the `Model` assumes +a `number_type` of `Float64`. +""" +mutable struct Model + md::ModelDef + mi::Union{Nothing, ModelInstance} + + function Model(number_type::DataType=Float64) + return new(ModelDef(number_type), nothing) + end + + # Create a copy of a model, e.g., to create marginal models + function Model(m::Model) + return new(deepcopy(m.md), nothing) + end +end + +""" + MarginalModel + +A Mimi `Model` whose results are obtained by subtracting results of one `base` Model +from those of another `marginal` Model` that has a difference of `delta`. +""" +struct MarginalModel + base::Model + marginal::Model + delta::Float64 + + function MarginalModel(base::Model, delta::Float64=1.0) + return new(base, Model(base), delta) + end +end + +function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) + return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta +end + +# +# 7. Reference types provide more convenient syntax for interrogating Components +# + +""" + ComponentReference + +A container for a component, for interacting with it within a model. +""" +struct ComponentReference + model::Model + comp_name::Symbol +end + +""" + VariableReference + +A container for a variable within a component, to improve connect_param! aesthetics, +by supporting subscripting notation via getindex & setindex . +""" +struct VariableReference + model::Model + comp_name::Symbol + var_name::Symbol +end diff --git a/src/core/defs.jl b/src/core/defs.jl index 48278d8c3..64d64d328 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -34,6 +34,21 @@ compdef(subcomps::SubcompsDef, comp_name::Symbol) = subcomps.comps_dict[comp_nam @delegate hascomp(md::ModelDef, comp_name::Symbol) => ccd @delegate compdef(md::ModelDef, comp_name::Symbol) => ccd +#= +Using new macro +@delegates(compdefs(subcomps::SubcompsDef) = values(subcomps.comps_dict), + ccd::CompositeComponentDef, md::ModelDef) + +@delegates(compkeys(subcomps::SubcompsDef) = keys(subcomps.comps_dict), + ccd::CompositeComponentDef, md::ModelDef) + +@delegates(hascomp(subcomps::SubcompsDef, comp_name::Symbol) = haskey(subcomps.comps_dict, comp_name), + ccd::CompositeComponentDef, md::ModelDef) + +@delegates(compdef(subcomps::SubcompsDef, comp_name::Symbol) = subcomps.comps_dict[comp_name], + ccd::CompositeComponentDef, md::ModelDef) +=# + function reset_compdefs(reload_builtins=true) empty!(_compdefs) @@ -187,7 +202,7 @@ function dimensions(ccd::CompositeComponentDef) end # use Set to eliminate duplicates - # TBD: what about ordering? + # TBD: If order is unimportant, store as a set instead? return collect(Set(dims)) end @@ -220,7 +235,7 @@ end function check_parameter_dimensions(md::ModelDef, value::AbstractArray, dims::Vector, name::Symbol) for dim in dims - if haskey(md, dim) + if has_dim(md, dim) if isa(value, NamedArray) labels = names(value, findnext(isequal(dim), dims, 1)) dim_vals = dim_keys(md, dim) @@ -249,10 +264,20 @@ function datum_size(md::ModelDef, comp_def::LeafComponentDef, datum_name::Symbol return datum_size end +has_dim(ccd::CompositeComponentDef, name::Symbol) = haskey(ccd.dimensions, name) +@delegate has_dim(md::ModelDef, name::Symbol) => ccd + +isuniform(ccd::CompositeComponentDef) = ccd.is_uniform +@delegate isuniform(md::ModelDef) => ccd + +set_uniform!(ccd::CompositeComponentDef, value::Bool) = (ccd.is_uniform = value) +@delegate set_uniform!(md::ModelDef, value::Bool) => ccd -dimensions(md::ModelDef) = md.dimensions dimensions(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] -dimension(md::ModelDef, name::Symbol) = md.dimensions[name] +@delegate dimensions(md::ModelDef) => ccd + +dimension(ccd::CompositeComponentDef, name::Symbol) = ccd.dimensions[name] +@delegate dimension(md::ModelDef, name::Symbol) => ccd dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dimensions(md)]) dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dimensions(md, dims)] @@ -264,10 +289,6 @@ dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) dim_values(md::ModelDef, name::Symbol) = collect(values(dimension(md, name))) dim_value_dict(md::ModelDef) = Dict([name => collect(values(dim)) for (name, dim) in dimensions(md)]) -Base.haskey(md::ModelDef, name::Symbol) = haskey(md.dimensions, name) - -isuniform(md::ModelDef) = md.is_uniform - # Helper function invoked when the user resets the time dimension with set_dimension! # This function calls set_run_period! on each component definition to reset the first and last values. @@ -304,22 +325,26 @@ end Set the values of `md` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. """ -function set_dimension!(md::ModelDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) - redefined = haskey(md, name) +@delegate set_dimension!(md::ModelDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => ccd + +function set_dimension!(cmd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + redefined = has_dim(ccd, name) if redefined @warn "Redefining dimension :$name" end if name == :time - md.is_uniform = isuniform(keys) + set_uniform!(md, isuniform(keys)) if redefined reset_run_periods!(md, keys[1], keys[end]) end end - dim = Dimension(keys) - md.dimensions[name] = dim - return dim + return set_dimension!(md.ccd, name, Dimension(keys)) +end + +function set_dimension!(ccd::CompositeComponentDef, name::Symbol, dim::Dimension) + ccd.dimensions[name] = dim end # helper functions used to determine if the provided time values are @@ -643,7 +668,7 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; before::NothingSymbol=nothing, after::NothingSymbol=nothing) # check that a time dimension has been set - if ! haskey(dimensions(md), :time) + if ! has_dim(md, :time) error("Cannot add component to model without first setting time dimension.") end diff --git a/src/core/delegate.jl b/src/core/delegate.jl new file mode 100644 index 000000000..bf75b3cab --- /dev/null +++ b/src/core/delegate.jl @@ -0,0 +1,147 @@ +# +# @delegate macro and support +# +using MacroTools + +function delegated_args(args::Vector) + newargs = [] + for a in args + if a isa Symbol + push!(newargs, a) + + elseif (@capture(a, var_::T_ = val_) || @capture(a, var_ = val_)) + push!(newargs, :($var = $var)) + + elseif @capture(a, var_::T_) + push!(newargs, var) + else + error("Unrecognized argument format: $a") + end + end + return newargs +end + +""" +Macro to define a method that simply delegate to a method with the same signature +but using the specified field name of the original first argument as the first arg +in the delegated call. That is, + + `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` + +expands to: + + `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` + +If a second expression is given, it is spliced in (basically to support "decache(m)") +""" +macro delegate(ex, other=nothing) + result = nothing + + if @capture(ex, fname_(varname_::T_, args__) => rhs_) + # @info "args: $args" + new_args = delegated_args(args) + result = quote + function $fname($varname::$T, $(args...)) + retval = $fname($varname.$rhs, $(new_args...)) + $other + return retval + end + end + elseif @capture(ex, fname_(varname_::T_, args__; kwargs__) => rhs_) + # @info "args: $args, kwargs: $kwargs" + new_args = delegated_args(args) + new_kwargs = delegated_args(kwargs) + result = quote + function $fname($varname::$T, $(args...); $(kwargs...)) + retval = $fname($varname.$rhs, $(new_args...); $(new_kwargs...)) + $other + return retval + end + end + end + + if result === nothing + error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") + end + return esc(result) +end + + +# _arg_default(t::Nothing) = nothing +# _arg_default(value::Symbol) = QuoteNode(value) +# _arg_default(value::Any) = value + +# function _compose_arg(arg_name, arg_type, slurp, default) +# decl = arg_name +# decl = arg_type != Any ? :($decl::$arg_type) : decl +# decl = slurp ? :($decl...) : decl +# decl = default !== nothing ? :($decl = $(_arg_default(default))) : decl +# return decl +# end + +# _compose_args(arg_tups::Vector{T}) where {T <: Tuple} = [_compose_arg(a...) for a in arg_tups] + +# N.B.: +# julia> splitdef(funcdef) +# Dict{Symbol,Any} with 5 entries: +# :name => :fname +# :args => Any[:(arg1::T)] # use splitarg() to bust out (arg_name, arg_type, slurp, default) +# :kwargs => Any[] +# :body => quote… +# :rtype => Any +# :whereparams => () + +# Example +#= +@macroexpand @Mimi.delegates( + function foo(cci::CompositeComponentInstance, bar, baz::Int, other::Float64=4.0; x=10) + println(other) + end, + + mi::ModelInstance, + m::Model +) + +=> + +quote + function foo(cci::CompositeComponentInstance, bar, baz::Int, other::Float64=4.0; x=10) + println(other) + end + function foo(mi::ModelInstance, bar, baz::Int, other::Float64=4.0; x=10)::Any + begin + return foo(mi.cci, bar, baz, other; x = x) + end + end + function foo(m::Model, bar, baz::Int, other::Float64=4.0; x=10)::Any + begin + return foo(m.mi, bar, baz, other; x = x) + end + end +end +=# +# macro delegates(args...) +# funcdef = args[1] +# args = collect(args[2:end]) + +# parts = splitdef(funcdef) +# fnargs = parts[:args] +# kwargs = parts[:kwargs] + +# result = quote $funcdef end # emit function as written, then add delegation funcs + +# for delegee in filter(x -> !(x isa LineNumberNode), args) +# (arg_name, arg_type, slurp, default) = splitarg(fnargs[1]) +# (delegee_name, arg_type, slurp, default) = splitarg(delegee) + +# fnargs[1] = :($delegee_name.$arg_name) +# call_args = [:($name) for (name, atype, slurp, default) in map(splitarg, fnargs)] +# call_kwargs = [:($name = $name) for (name, atype, slurp, default) in map(splitarg, kwargs)] + +# parts[:body] = quote return $(parts[:name])($(call_args...); $(call_kwargs...)) end +# fnargs[1] = delegee +# push!(result.args, MacroTools.combinedef(parts)) +# end + +# return esc(result) +# end diff --git a/src/core/instances.jl b/src/core/instances.jl index 121c95a52..94099fefa 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -181,7 +181,7 @@ variables(mi::ModelInstance, comp_name::Symbol) = variables(compinstance(mi, com variables(ci::ComponentInstance) = ci.variables -@delegate variables(mi::ModelInstance) => ci +@delegate variables(mi::ModelInstance) => cci function variables(m::Model) if m.mi === nothing @@ -204,7 +204,9 @@ Return an iterator over the parameters in `ci`. """ parameters(ci::ComponentInstance) = ci.parameters -@delegate parameters(m::Model) => cci +@delegate parameters(m::Model) => mi + +@delegate parameters(mi::ModelInstance) => cci function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) @@ -321,6 +323,7 @@ end function _run_components(mi::ModelInstance, clock::Clock, firsts::Vector{Int}, lasts::Vector{Int}, comp_clocks::Vector{Clock{T}}) where {T <: AbstractTimestep} + @info "_run_components: firsts: $firsts, lasts: $lasts" comp_instances = components(mi) tups = collect(zip(comp_instances, firsts, lasts, comp_clocks)) diff --git a/src/core/types.jl b/src/core/types.jl index 950208050..823425efa 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,72 +1,3 @@ -# -# 0. Macro used in several places, so centralized here even though not type-related -# -using MacroTools - -function delegated_args(args::Vector) - newargs = [] - @info "delegated_args: $args" - for a in args - if a isa Symbol - push!(newargs, a) - - elseif (@capture(a, var_::T_ = val_) || @capture(a, var_ = val_)) - push!(newargs, :($var = $var)) - - elseif @capture(a, var_::T_) - push!(newargs, var) - else - error("Unrecognized argument format: $a") - end - end - return newargs -end - -""" -Macro to define a method that simply delegate to a method with the same signature -but using the specified field name of the original first argument as the first arg -in the delegated call. That is, - - `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` - -expands to: - - `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` - -If a second expression is given, it is spliced in (basically to support "decache(m)") -""" -macro delegate(ex, other=nothing) - result = nothing - - if @capture(ex, fname_(varname_::T_, args__) => rhs_) - # @info "args: $args" - new_args = delegated_args(args) - result = quote - function $fname($varname::$T, $(args...)) - retval = $fname($varname.$rhs, $(new_args...)) - $other - return retval - end - end - elseif @capture(ex, fname_(varname_::T_, args__; kwargs__) => rhs_) - # @info "args: $args, kwargs: $kwargs" - new_args = delegated_args(args) - new_kwargs = delegated_args(kwargs) - result = quote - function $fname($varname::$T, $(args...); $(kwargs...)) - retval = $fname($varname.$rhs, $(new_args...); $(new_kwargs...)) - $other - return retval - end - end - end - - if result === nothing - error("Calls to @delegate must be of the form 'func(obj, args...) => X', where X is a field of obj to delegate to'. Expression was: $ex") - end - return esc(result) -end - # # 1. Types supporting parameterized Timestep and Clock objects # @@ -303,6 +234,7 @@ mutable struct ComponentDef{T <: SubcompsDefTypes} <: NamedDef dimensions::OrderedDict{Symbol, DimensionDef} first::Union{Nothing, Int} last::Union{Nothing, Int} + is_uniform::Bool # info about sub-components, or nothing subcomps::T @@ -323,6 +255,7 @@ mutable struct ComponentDef{T <: SubcompsDefTypes} <: NamedDef self.parameters = OrderedDict{Symbol, DatumDef}() self.dimensions = OrderedDict{Symbol, DimensionDef}() self.first = self.last = nothing + self.is_uniform = true self.subcomps = subcomps return self end @@ -382,14 +315,12 @@ is_composite(comp::ComponentDef) = !is_leaf(comp) mutable struct ModelDef ccd::ComponentDef - dimensions::Dict{Symbol, Dimension} + dimensions::Dict{Symbol, Dimension} # TBD: use the one in ccd instead number_type::DataType - is_uniform::Bool function ModelDef(ccd::CompositeComponentDef, number_type::DataType=Float64) dimensions = Dict{Symbol, Dimension}() - is_uniform = true - return new(ccd, dimensions, number_type, is_uniform) + return new(ccd, dimensions, number_type) end function ModelDef(number_type::DataType=Float64) diff --git a/test/test_composite.jl b/test/test_composite.jl index 0afef5a9a..906bc48f2 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -11,30 +11,33 @@ reset_compdefs() @defcomp Comp1 begin - par1 = Parameter(index=[time]) # external input - var1 = Variable(index=[time]) # computed + par_1_1 = Parameter(index=[time]) # external input + var_1_1 = Variable(index=[time]) # computed function run_timestep(p, v, d, t) - v.var1[t] = p.par1[t] + @info "Comp1 run_timestep" + v.var_1_1[t] = p.par_1_1[t] end end @defcomp Comp2 begin - par1 = Parameter(index=[time]) # connected to Comp1.var1 - par2 = Parameter(index=[time]) # external input - var1 = Variable(index=[time]) # computed + par_2_1 = Parameter(index=[time]) # connected to Comp1.var_1_1 + par_2_2 = Parameter(index=[time]) # external input + var_2_1 = Variable(index=[time]) # computed function run_timestep(p, v, d, t) - v.var1[t] = p.par1[t] + p.par2[t] + @info "Comp2 run_timestep" + v.var_2_1[t] = p.par_2_1[t] + p.par_2_2[t] end end @defcomp Comp3 begin - par1 = Parameter(index=[time]) # connected to Comp2.var1 - var1 = Variable(index=[time]) # external output + par_3_1 = Parameter(index=[time]) # connected to Comp2.var_2_1 + var_3_1 = Variable(index=[time]) # external output function run_timestep(p, v, d, t) - v.var1[t] = p.par1[t] * 2 + @info "Comp3 run_timestep" + v.var_3_1[t] = p.par_3_1[t] * 2 end end @@ -49,15 +52,15 @@ let calling_module = @__MODULE__ # TBD: need to implement this to create connections and default value bindings::Vector{Pair{DatumReference, BindingTypes}} = [ - DatumReference(Comp2, :par1) => 5, # bind Comp1.par1 to constant value of 5 - DatumReference(Comp2, :par1) => DatumReference(Comp1, :var1), # connect target Comp2.par1 to source Comp1.var1 - DatumReference(Comp3, :par1) => DatumReference(Comp2, :var1) + DatumReference(Comp1, :par_1_1) => 5, # bind Comp1.par_1_1 to constant value of 5 + DatumReference(Comp2, :par_2_2) => DatumReference(Comp1, :var_1_1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + DatumReference(Comp3, :par_3_1) => DatumReference(Comp2, :var_2_1) ] exports = [ - DatumReference(Comp1, :par1) => :c1p1, # i.e., export Comp1.par1 as :c1p1 - DatumReference(Comp2, :par2) => :c2p2, - DatumReference(Comp3, :var1) => :c3v1 + DatumReference(Comp1, :par_1_1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + DatumReference(Comp2, :par2_2) => :c2p2, + DatumReference(Comp3, :var_3_1) => :c3v1 ] subcomps = SubcompsDef(comps, bindings, exports) @@ -71,13 +74,19 @@ m = MyComposite md = m.md ccd = md.ccd -set_param!(m, :Comp1, :par1, zeros(length(time_labels(md)))) -connect_param!(md, :Comp2, :par1, :Comp1, :var1) -connect_param!(md, :Comp2, :par2, :Comp1, :var1) -connect_param!(md, :Comp3, :par1, :Comp2, :var1) +set_param!(m, :Comp1, :par_1_1, zeros(length(time_labels(md)))) +connect_param!(md, :Comp2, :par_2_1, :Comp1, :var_1_1) +connect_param!(md, :Comp2, :par_2_2, :Comp1, :var_1_1) +connect_param!(md, :Comp3, :par_3_1, :Comp2, :var_2_1) build(MyComposite) end # module +m = TestComposite.m +md = m.md +ccd = md.ccd +mi = m.mi +cci = mi.cci + nothing \ No newline at end of file From e5108b8687eb5442986c6c325fd95cd59e18a161 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 29 Nov 2018 12:21:27 -0800 Subject: [PATCH 18/81] Trial version based on Classes.jl. --- src/Mimi.jl | 5 +- src/core/build.jl | 11 +- src/core/classes.jl | 324 ------------------------- src/core/defs.jl | 11 +- src/core/delegate.jl | 27 +-- src/core/types2.jl | 531 +++++++++++++++++++++++++++++++++++++++++ test/test_composite.jl | 4 +- 7 files changed, 567 insertions(+), 346 deletions(-) delete mode 100644 src/core/classes.jl create mode 100644 src/core/types2.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index 55a981f5c..7ee0fdb11 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -1,5 +1,6 @@ module Mimi +using Classes using DataFrames using DataStructures using Distributions @@ -41,7 +42,9 @@ export update_params!, variables -include("core/types.jl") +# TBD: testing new @classes in classes.jl +include("core/types2.jl") + include("core/delegate.jl") # After loading types and delegation macro, the rest is alphabetical diff --git a/src/core/build.jl b/src/core/build.jl index b25c0969c..c221b654a 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -1,7 +1,7 @@ connector_comp_name(i::Int) = Symbol("ConnectorComp$i") # Return the datatype to use for instance variables/parameters -function _instance_datatype(md::ModelDef, def::DatumDef) +function _instance_datatype(md::ModelDef, def::DatumDef) # TBD: supertype(DatumDef) or _DatumDef_ dtype = def.datatype == Number ? number_type(md) : def.datatype dims = dimensions(def) num_dims = dim_count(def) @@ -27,7 +27,7 @@ function _instance_datatype(md::ModelDef, def::DatumDef) end # Create the Ref or Array that will hold the value(s) for a Parameter or Variable -function _instantiate_datum(md::ModelDef, def::DatumDef) +function _instantiate_datum(md::ModelDef, def::DatumDef) # TBD: supertype(DatumDef) or _DatumDef_ dtype = _instance_datatype(md, def) dims = dimensions(def) num_dims = length(dims) @@ -109,6 +109,8 @@ function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{ end +# TBD: define two methods, one on comp_def::ComponentDef and other on comp_def::CompositeComponentDef + # Recursively instantiate all variables and store refs in the given dict. function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) comp_name = name(comp_def) @@ -127,6 +129,7 @@ function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{ end end +# TBD: Define only for CompositeComponentDef? # Recursively collect all parameters with connections to allocated storage for variablesa function _collect_params(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) if is_composite(comp_def) @@ -177,9 +180,13 @@ function _save_dim_dict_reference(mi::ModelInstance) return nothing end +# TBD: +# _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) = _combine_exported_pars(comp_def, par_dict) + function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @info "Instantiating params for $(comp_def.comp_id)" + # TBD: drop if, use only else branch if is_composite(comp_def) return _combine_exported_pars(comp_def, par_dict) diff --git a/src/core/classes.jl b/src/core/classes.jl deleted file mode 100644 index d86ac2732..000000000 --- a/src/core/classes.jl +++ /dev/null @@ -1,324 +0,0 @@ -# Objects with a `name` attribute -@class Named begin - name::Symbol -end - -@method name(obj::Named) = obj.name - -@class DimensionDef <: Named - -# Similar structure is used for variables and parameters (parameters has `default`) -@class mutable VarDef <: Named begin - datatype::DataType - dimensions::Vector{Symbol} - description::String - unit::String -end - -@class mutable ParDef <: VarDef begin - # ParDef adds a default value, which can be specified in @defcomp - default::Any -end - - -@class mutable ComponentDef <: Named begin - comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) - name::Symbol # Union{Nothing, Symbol} ? - variables::OrderedDict{Symbol, VarDef} - parameters::OrderedDict{Symbol, ParDef} - dimensions::OrderedDict{Symbol, DimensionDef} - first::Union{Nothing, Int} - last::Union{Nothing, Int} - is_uniform::Bool - - # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) - if (is_leaf(self) && comp_id === nothing) - error("Leaf ComponentDef objects must have a Symbol name (not nothing)") - end - - self.comp_id = comp_id - self.name = comp_name - self.variables = OrderedDict{Symbol, DatumDef}() - self.parameters = OrderedDict{Symbol, DatumDef}() - self.dimensions = OrderedDict{Symbol, DimensionDef}() - self.first = self.last = nothing - self.is_uniform = true - return self - end - - function ComponentDef(comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) - return ComponentDef(new(), comp_id, comp_name) - end - - # ComponentDef() = ComponentDef(nothing, gensym("anonymous")) -end - -@class mutable CompositeDef <: ComponentDef begin - comps_dict::OrderedDict{Symbol, _ComponentDef_} - bindings::Vector{Pair{DatumReference, BindingTypes}} - exports::Vector{Pair{DatumReference, Symbol}} - - internal_param_conns::Vector{InternalParameterConnection} - external_param_conns::Vector{ExternalParameterConnection} - external_params::Dict{Symbol, ModelParameter} - - # Names of external params that the ConnectorComps will use as their :input2 parameters. - backups::Vector{Symbol} - - sorted_comps::Union{Nothing, Vector{Symbol}} - - function CompositeDef(comps::Vector{T}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} - self = new() - - # superclass initialization - # ComponentDef(self, comp_id, comp_name) - - self.comps_dict = OrderedDict{Symbol, T}([name(cd) => cd for cd in comps]) - self.bindings = bindings - self.exports = exports - self.internal_param_conns = Vector{InternalParameterConnection}() - self.external_param_conns = Vector{ExternalParameterConnection}() - self.external_params = Dict{Symbol, ModelParameter}() - self.backups = Vector{Symbol}() - self.sorted_comps = nothing - - return self - end - - function CompositeDef() - comps = Vector{<: _ComponentDef_}() - bindings = Vector{Pair{DatumReference, BindingTypes}}() - exports = Vector{Pair{DatumReference, Symbol}}() - return CompositeDef(comps, bindings, exports) - end -end - -@method is_leaf(comp::ComponentDef) = true -@method is_leaf(comp::CompositeDef) = false -@method is_composite(comp::ComponentDef) = !is_leaf(comp) - -# TBD: Does a ModelDef contain a CompositeDef, or is it a subclass? - -@class mutable ModelDef <: CompositeDef begin - dimensions::Dict{Symbol, Dimension} # TBD: use the one in ccd instead - number_type::DataType - - # TBD: these methods assume sub-elements rather than subclasses - function ModelDef(ccd::CompositeDef, number_type::DataType=Float64) - dimensions = Dict{Symbol, Dimension}() - return new(ccd, dimensions, number_type) - end - - function ModelDef(number_type::DataType=Float64) - # passes an anonymous top-level (composite) ComponentDef - return ModelDef(ComponentDef(), number_type) - end -end - -# -# 5. Types supporting instantiated models and their components -# - -# Supertype for variables and parameters in component instances -@class InstanceData{NT <: NamedTuple} begin - nt::NT -end - -@class InstanceParameters{NT <: NamedTuple} <: InstanceData - -function InstanceParameters(names, types, values) - NT = NamedTuple{names, types} - InstanceParameters{NT}(NT(values)) -end - -function InstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - InstanceParameters{NT}(NT(values)) -end - - -@class InstanceVariables{NT <: NamedTuple} <: InstanceData - -function InstanceVariables(names, types, values) - NT = NamedTuple{names, types} - InstanceVariables{NT}(NT(values)) -end - -function InstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - InstanceVariables{NT}(NT(values)) -end - -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimDict - dict::Dict{Symbol, Vector{Int}} -end - -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimDict of dimensions by name -# as the "d" parameter. -@inline function Base.getproperty(dimdict::DimDict, property::Symbol) - return getfield(dimdict, :dict)[property] -end - -@method nt(obj::InstanceData) = getfield(obj, :nt) -@method Base.names(obj::InstanceData) = keys(nt(obj)) -@method Base.values(obj::InstanceData) = values(nt(obj)) -@method types(obj::InstanceData) = typeof(nt(obj)).parameters[2].parameters - -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin - comp_name::Symbol - comp_id::ComponentId - variables::TV - parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} - first::Union{Nothing, Int} - last::Union{Nothing, Int} - init::Union{Nothing, Function} - run_timestep::Union{Nothing, Function} - - function ComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where - {TV <: InstanceVariables, TP <: InstanceParameters} - - self = new{TV, TP}() - self.comp_id = comp_id = comp_def.comp_id - self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage - self.variables = vars - self.parameters = pars - self.first = comp_def.first - self.last = comp_def.last - - comp_module = Base.eval(Main, comp_id.module_name) - - # The try/catch allows components with no run_timestep function (as in some of our test cases) - # All ComponentInstances use a standard method that just loops over inner components. - # TBD: use FunctionWrapper here? - function get_func(name) - func_name = Symbol("$(name)_$(self.comp_name)") - try - Base.eval(comp_module, func_name) - catch err - nothing - end - end - - # `is_composite` indicates a ComponentInstance used to store summary - # data for ComponentInstance and is not itself runnable. - self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") - self.init = is_composite(self) ? nothing : get_func("init") - - return self - end -end - -function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) -end - -@class mutable CompositeInstance <: ComponentInstance begin - comps_dict::OrderedDict{Symbol, _ComponentInstance_} - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - clocks::Vector{Clock} - - function CompositeInstance(comps::Vector{T}) where {T <: _ComponentInstance_} - self = new() - self.comps_dict = OrderedDict{Symbol, _ComponentInstance_}([ci.comp_name => ci for ci in comps]) - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - self.clocks = Vector{Clock}() - return self - end -end - -@method is_leaf(ci::ComponentInstance) = true -@method is_leaf(ci::CompositeInstance) = false -@method is_composite(ci::ComponentInstance) = !is_leaf(ci) - -# TBD: @class or container? -# ModelInstance holds the built model that is ready to be run -mutable struct ModelInstance <: CompositeInstance - md::ModelDef - cci::Union{Nothing, CompositeInstance} - - function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) - return new(md, cci) - end -end - -# -# 6. User-facing Model types providing a simplified API to model definitions and instances. -# -""" - Model - -A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). -This `Model` can be created with the optional keyword argument `number_type` indicating -the default type of number used for the `ModelDef`. If not specified the `Model` assumes -a `number_type` of `Float64`. -""" -mutable struct Model - md::ModelDef - mi::Union{Nothing, ModelInstance} - - function Model(number_type::DataType=Float64) - return new(ModelDef(number_type), nothing) - end - - # Create a copy of a model, e.g., to create marginal models - function Model(m::Model) - return new(deepcopy(m.md), nothing) - end -end - -""" - MarginalModel - -A Mimi `Model` whose results are obtained by subtracting results of one `base` Model -from those of another `marginal` Model` that has a difference of `delta`. -""" -struct MarginalModel - base::Model - marginal::Model - delta::Float64 - - function MarginalModel(base::Model, delta::Float64=1.0) - return new(base, Model(base), delta) - end -end - -function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) - return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta -end - -# -# 7. Reference types provide more convenient syntax for interrogating Components -# - -""" - ComponentReference - -A container for a component, for interacting with it within a model. -""" -struct ComponentReference - model::Model - comp_name::Symbol -end - -""" - VariableReference - -A container for a variable within a component, to improve connect_param! aesthetics, -by supporting subscripting notation via getindex & setindex . -""" -struct VariableReference - model::Model - comp_name::Symbol - var_name::Symbol -end diff --git a/src/core/defs.jl b/src/core/defs.jl index 64d64d328..b9206d9c4 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -86,11 +86,13 @@ function last_period(comp_def::CompositeComponentDef) nothing # use model's last period end +# TBD: @method with comp_def first, or use comp_def::supertype(ComponentDef) function first_period(md::ModelDef, comp_def::ComponentDef) period = first_period(comp_def) return period === nothing ? time_labels(md)[1] : period end +# TBD: @method with comp_def first, or use comp_def::supertype(ComponentDef) function last_period(md::ModelDef, comp_def::ComponentDef) period = last_period(comp_def) return period === nothing ? time_labels(md)[end] : period @@ -101,11 +103,13 @@ compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name +# TBD: @method name(obj::DatumReference) = obj.datum_name name(dr::DatumReference) = dr.datum_name + @delegate compname(dr::DatumReference) => comp_id @delegate compmodule(dr::DatumReference) => comp_id -is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), name(dr)) +is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), name(dr)) is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), name(dr)) function Base.show(io::IO, comp_id::ComponentId) @@ -125,7 +129,9 @@ number_type(md::ModelDef) = md.number_type @delegate numcomponents(md::ModelDef) => ccd numcomponents(comp_def::LeafComponentDef) = 0 # no subcomponents -numcomponents(comp_def::CompositeComponentDef) = numcomponents(comp_def.subcomps) +numcomponents(comp_def::CompositeComponentDef) = numcomponents(comp_def.subcomps) # TBD: redefine + +# TBD: delete numcomponents(info::SubcompsDef) = length(info.comps_dict) function dump_components() @@ -206,6 +212,7 @@ function dimensions(ccd::CompositeComponentDef) return collect(Set(dims)) end +# TBD: @method dimensions(def::DatumDef) = def.dimensions # TBD: handle CompositeComponentDef diff --git a/src/core/delegate.jl b/src/core/delegate.jl index bf75b3cab..11ea637be 100644 --- a/src/core/delegate.jl +++ b/src/core/delegate.jl @@ -36,28 +36,26 @@ If a second expression is given, it is spliced in (basically to support "decache """ macro delegate(ex, other=nothing) result = nothing + other = (other === nothing ? [] : [other]) # make vector so $(other...) disappears if empty - if @capture(ex, fname_(varname_::T_, args__) => rhs_) + if (@capture(ex, fname_(varname_::T_, args__; kwargs__) => rhs_) || + @capture(ex, fname_(varname_::T_, args__) => rhs_)) # @info "args: $args" new_args = delegated_args(args) - result = quote - function $fname($varname::$T, $(args...)) - retval = $fname($varname.$rhs, $(new_args...)) - $other - return retval - end - end - elseif @capture(ex, fname_(varname_::T_, args__; kwargs__) => rhs_) - # @info "args: $args, kwargs: $kwargs" - new_args = delegated_args(args) - new_kwargs = delegated_args(kwargs) + + if kwargs === nothing + kwargs = new_kwargs = [] + else + new_kwargs = delegated_args(kwargs) + end + result = quote function $fname($varname::$T, $(args...); $(kwargs...)) retval = $fname($varname.$rhs, $(new_args...); $(new_kwargs...)) - $other + $(other...) return retval end - end + end end if result === nothing @@ -66,7 +64,6 @@ macro delegate(ex, other=nothing) return esc(result) end - # _arg_default(t::Nothing) = nothing # _arg_default(value::Symbol) = QuoteNode(value) # _arg_default(value::Any) = value diff --git a/src/core/types2.jl b/src/core/types2.jl new file mode 100644 index 000000000..b07b62d5a --- /dev/null +++ b/src/core/types2.jl @@ -0,0 +1,531 @@ +using Classes +using DataStructures + +# +# 1. Types supporting parameterized Timestep and Clock objects +# + +abstract type AbstractTimestep end + +struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep + t::Int +end + +struct VariableTimestep{TIMES} <: AbstractTimestep + t::Int + current::Int + + function VariableTimestep{TIMES}(t::Int = 1) where {TIMES} + # The special case below handles when functions like next_step step beyond + # the end of the TIMES array. The assumption is that the length of this + # last timestep, starting at TIMES[end], is 1. + current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t] + + return new(t, current) + end +end + +mutable struct Clock{T <: AbstractTimestep} + ts::T + + function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T + return new(FixedTimestep{FIRST, STEP, LAST}(1)) + end + + function Clock{T}(TIMES::NTuple{N, Int} where N) where T + return new(VariableTimestep{TIMES}()) + end +end + +mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} + data::Array{T, N} + + function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} + return new(d) + end + + function TimestepArray{T_TS, T, N}(lengths::Int...) where {T_TS, T, N} + return new(Array{T, N}(undef, lengths...)) + end +end + +# Since these are the most common cases, we define methods (in time.jl) +# specific to these type aliases, avoiding some of the inefficiencies +# associated with an arbitrary number of dimensions. +const TimestepMatrix{T_TS, T} = TimestepArray{T_TS, T, 2} +const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1} + +# +# 2. Dimensions +# + +abstract type AbstractDimension end + +const DimensionKeyTypes = Union{AbstractString, Symbol, Int, Float64} +const DimensionRangeTypes = Union{UnitRange{Int}, StepRange{Int, Int}} + +struct Dimension{T <: DimensionKeyTypes} <: AbstractDimension + dict::OrderedDict{T, Int} + + function Dimension(keys::Vector{T}) where {T <: DimensionKeyTypes} + dict = OrderedDict(collect(zip(keys, 1:length(keys)))) + return new{T}(dict) + end + + function Dimension(rng::T) where {T <: DimensionRangeTypes} + return Dimension(collect(rng)) + end + + Dimension(i::Int) = Dimension(1:i) + + # Support Dimension(:foo, :bar, :baz) + function Dimension(keys::T...) where {T <: DimensionKeyTypes} + vector = [key for key in keys] + return Dimension(vector) + end +end + +# +# Simple optimization for ranges since indices are computable. +# Unclear whether this is really any better than simply using +# a dict for all cases. Might scrap this in the end. +# +mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension + range::T + end + +# +# 3. Types supporting Parameters and their connections +# +abstract type ModelParameter end + +# TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? + +mutable struct ScalarModelParameter{T} <: ModelParameter + value::T + + function ScalarModelParameter{T}(value::T) where T + new(value) + end + + function ScalarModelParameter{T1}(value::T2) where {T1, T2} + try + new(T1(value)) + catch err + error("Failed to convert $value::$T2 to $T1") + end + end +end + +mutable struct ArrayModelParameter{T} <: ModelParameter + values::T + dimensions::Vector{Symbol} # if empty, we don't have the dimensions' name information + + function ArrayModelParameter{T}(values::T, dims::Vector{Symbol}) where T + new(values, dims) + end +end + +ScalarModelParameter(value) = ScalarModelParameter{typeof(value)}(value) + +Base.convert(::Type{ScalarModelParameter{T}}, value::Number) where {T} = ScalarModelParameter{T}(T(value)) + +Base.convert(::Type{T}, s::ScalarModelParameter{T}) where {T} = T(s.value) + +ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(value)}(value, dims) + + +abstract type AbstractConnection end + +struct InternalParameterConnection <: AbstractConnection + src_comp_name::Symbol + src_var_name::Symbol + dst_comp_name::Symbol + dst_par_name::Symbol + ignoreunits::Bool + backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing + offset::Int + + function InternalParameterConnection(src_comp::Symbol, src_var::Symbol, dst_comp::Symbol, dst_par::Symbol, + ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) + self = new(src_comp, src_var, dst_comp, dst_par, ignoreunits, backup, offset) + return self + end +end + +struct ExternalParameterConnection <: AbstractConnection + comp_name::Symbol + param_name::Symbol # name of the parameter in the component + external_param::Symbol # name of the parameter stored in md.ccd.external_params +end + +# +# 4. Types supporting structural definition of models and their components +# + +# To identify components, we create a variable with the name of the component +# whose value is an instance of this type, e.g. +# const global adder = ComponentId(module_name, comp_name) +struct ComponentId + module_name::Symbol + comp_name::Symbol +end + +ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) + +# +# TBD: consider a naming protocol that adds Cls to class struct names +# so it's obvious in the code. +# + +# Objects with a `name` attribute +@class NamedObj begin + name::Symbol +end + +@method name(obj::NamedObj) = obj.name + +# TBD +# @class DatumReference <: NamedObj +# comp_id::ComponentId +# end +# Stores references to the name of a component variable or parameter +struct DatumReference + comp_id::ComponentId + datum_name::Symbol +end + +# *Def implementation doesn't need to be performance-optimized since these +# are used only to create *Instance objects that are used at run-time. With +# this in mind, we don't create dictionaries of vars, params, or dims in the +# ComponentDef since this would complicate matters if a user decides to +# add/modify/remove a component. Instead of maintaining a secondary dict, +# we just iterate over sub-components at run-time as needed. + +global const BindingTypes = Union{Int, Float64, DatumReference} + +@class DimensionDef <: NamedObj + +# Similar structure is used for variables and parameters (parameters merely adds `default`) +@class mutable DatumDef <: NamedObj begin + datatype::DataType + dimensions::Vector{Symbol} + description::String + unit::String +end + +@class mutable VariableDef <: DatumDef + +@class mutable ParameterDef <: DatumDef begin + # ParameterDef adds a default value, which can be specified in @defcomp + default::Any +end + +@class mutable ComponentDef <: NamedObj begin + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) + variables::OrderedDict{Symbol, VariableDef} + parameters::OrderedDict{Symbol, ParameterDef} + dimensions::OrderedDict{Symbol, DimensionDef} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + is_uniform::Bool + + # ComponentDefs are created "empty". Elements are subsequently added. + function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) + if (is_leaf(self) && comp_id === nothing) + error("Leaf ComponentDef objects must have a Symbol name (not nothing)") + end + + self.comp_id = comp_id + self.name = comp_name + self.variables = OrderedDict{Symbol, VariableDef}() + self.parameters = OrderedDict{Symbol, ParameterDef}() + self.dimensions = OrderedDict{Symbol, DimensionDef}() + self.first = self.last = nothing + self.is_uniform = true + return self + end + + function ComponentDef(comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) + return ComponentDef(new(), comp_id, comp_name) + end + + # ComponentDef() = ComponentDef(nothing, gensym("anonymous")) +end + +@class mutable CompositeComponentDef <: ComponentDef begin + comps_dict::OrderedDict{Symbol, _ComponentDef_} + bindings::Vector{Pair{DatumReference, BindingTypes}} + exports::Vector{Pair{DatumReference, Symbol}} + + internal_param_conns::Vector{InternalParameterConnection} + external_param_conns::Vector{ExternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} + + # Names of external params that the ConnectorComps will use as their :input2 parameters. + backups::Vector{Symbol} + + sorted_comps::Union{Nothing, Vector{Symbol}} + + function CompositeComponentDef(comps::Vector{T}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} + self = new() + + # superclass initialization + # ComponentDef(self, comp_id, comp_name) + + self.comps_dict = OrderedDict{Symbol, T}([name(cd) => cd for cd in comps]) + self.bindings = bindings + self.exports = exports + self.internal_param_conns = Vector{InternalParameterConnection}() + self.external_param_conns = Vector{ExternalParameterConnection}() + self.external_params = Dict{Symbol, ModelParameter}() + self.backups = Vector{Symbol}() + self.sorted_comps = nothing + + return self + end + + function CompositeComponentDef() + comps = Vector{<: _ComponentDef_}() + bindings = Vector{Pair{DatumReference, BindingTypes}}() + exports = Vector{Pair{DatumReference, Symbol}}() + return CompositeComponentDef(comps, bindings, exports) + end +end + +# Move to defs.jl? + +@method is_leaf(comp::ComponentDef) = true +@method is_leaf(comp::CompositeComponentDef) = false +@method is_composite(comp::ComponentDef) = !is_leaf(comp) + +# TBD: Does a ModelDef contain a CompositeComponentDef, or is it a subclass? + +@class mutable ModelDef <: CompositeComponentDef begin + dimensions2::Dict{Symbol, Dimension} # TBD: use the one in ccd instead + number_type::DataType + + # TBD: these methods assume sub-elements rather than subclasses + function ModelDef(ccd::CompositeComponentDef, number_type::DataType=Float64) + dimensions = Dict{Symbol, Dimension}() + return new(ccd, dimensions, number_type) + end + + function ModelDef(number_type::DataType=Float64) + # passes an anonymous top-level (composite) ComponentDef + return ModelDef(ComponentDef(), number_type) + end +end + +# +# 5. Types supporting instantiated models and their components +# + +# Supertype for variables and parameters in component instances +@class ComponentInstanceData{NT <: NamedTuple} begin + nt::NT +end + +@class ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData + +@class ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData + +function ComponentInstanceParameters(names, types, values) + NT = NamedTuple{names, types} + ComponentInstanceParameters{NT}(NT(values)) +end + +function ComponentInstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + ComponentInstanceParameters{NT}(NT(values)) +end + +function ComponentInstanceVariables(names, types, values) + NT = NamedTuple{names, types} + ComponentInstanceVariables{NT}(NT(values)) +end + +function ComponentInstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} + ComponentInstanceVariables{NT}(NT(values)) +end + +# Move to instances.jl? + +@method nt(obj::ComponentInstanceData) = getfield(obj, :nt) +@method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) +@method Base.values(obj::ComponentInstanceData) = values(nt(obj)) +@method types(obj::ComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters + +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin + comp_name::Symbol + comp_id::ComponentId + variables::TV + parameters::TP + dim_dict::Dict{Symbol, Vector{Int}} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + init::Union{Nothing, Function} + run_timestep::Union{Nothing, Function} + + function ComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = new{TV, TP}() + self.comp_id = comp_id = comp_def.comp_id + self.comp_name = name + self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.variables = vars + self.parameters = pars + self.first = comp_def.first + self.last = comp_def.last + + comp_module = Base.eval(Main, comp_id.module_name) + + # The try/catch allows components with no run_timestep function (as in some of our test cases) + # All ComponentInstances use a standard method that just loops over inner components. + # TBD: use FunctionWrapper here? + function get_func(name) + func_name = Symbol("$(name)_$(self.comp_name)") + try + Base.eval(comp_module, func_name) + catch err + nothing + end + end + + # `is_composite` indicates a ComponentInstance used to store summary + # data for ComponentInstance and is not itself runnable. + self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") + self.init = is_composite(self) ? nothing : get_func("init") + + return self + end +end + +function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=name(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) +end + +@class mutable CompositeInstance <: ComponentInstance begin + comps_dict::OrderedDict{Symbol, _ComponentInstance_} + firsts::Vector{Int} # in order corresponding with components + lasts::Vector{Int} + clocks::Vector{Clock} + + function CompositeInstance(comps::Vector{T}) where {T <: _ComponentInstance_} + self = new() + self.comps_dict = OrderedDict{Symbol, _ComponentInstance_}([ci.comp_name => ci for ci in comps]) + self.firsts = Vector{Int}() + self.lasts = Vector{Int}() + self.clocks = Vector{Clock}() + return self + end +end + +@method is_leaf(ci::ComponentInstance) = true +@method is_leaf(ci::CompositeInstance) = false +@method is_composite(ci::ComponentInstance) = !is_leaf(ci) + +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimDict + dict::Dict{Symbol, Vector{Int}} +end + +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimDict of dimensions by name +# as the "d" parameter. +@inline function Base.getproperty(dimdict::DimDict, property::Symbol) + return getfield(dimdict, :dict)[property] +end + +nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) +Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) +Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) +types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters + +# TBD: should this be @class or struct? +# ModelInstance holds the built model that is ready to be run +mutable struct ModelInstance + md::ModelDef + cci::Union{Nothing, CompositeComponentInstance} + + function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) + return new(md, cci) + end +end + +# +# 6. User-facing Model types providing a simplified API to model definitions and instances. +# +""" + Model + +A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). +This `Model` can be created with the optional keyword argument `number_type` indicating +the default type of number used for the `ModelDef`. If not specified the `Model` assumes +a `number_type` of `Float64`. +""" +mutable struct Model + md::ModelDef + mi::Union{Nothing, ModelInstance} + + function Model(number_type::DataType=Float64) + return new(ModelDef(number_type), nothing) + end + + # Create a copy of a model, e.g., to create marginal models + function Model(m::Model) + return new(deepcopy(m.md), nothing) + end +end + +""" + MarginalModel + +A Mimi `Model` whose results are obtained by subtracting results of one `base` Model +from those of another `marginal` Model` that has a difference of `delta`. +""" +struct MarginalModel + base::Model + marginal::Model + delta::Float64 + + function MarginalModel(base::Model, delta::Float64=1.0) + return new(base, Model(base), delta) + end +end + +function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) + return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta +end + +# +# 7. Reference types provide more convenient syntax for interrogating Components +# + +""" + ComponentReference + +A container for a component, for interacting with it within a model. +""" +struct ComponentReference + model::Model + comp_name::Symbol +end + +""" + VariableReference + +A container for a variable within a component, to improve connect_param! aesthetics, +by supporting subscripting notation via getindex & setindex . +""" +struct VariableReference + model::Model + comp_name::Symbol + var_name::Symbol +end diff --git a/test/test_composite.jl b/test/test_composite.jl index 906bc48f2..46e89c272 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,8 +4,8 @@ using Test using Mimi import Mimi: - reset_compdefs, compdef, ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, - SubcompsDef, SubcompsDefTypes, build, time_labels + ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, + SubcompsDef, SubcompsDefTypes, build, time_labels, reset_compdefs, compdef reset_compdefs() From fd9039342377d8f203653910112b9e70a41797f0 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 29 Nov 2018 16:38:54 -0800 Subject: [PATCH 19/81] WIP - version using Classes.jl --- src/core/build.jl | 10 +- src/core/connections.jl | 6 +- src/core/defs.jl | 129 +++++++++---------------- src/core/instances.jl | 6 +- src/core/types.jl | 6 +- src/core/types2.jl | 21 ++-- src/utils/graph.jl | 4 +- test/test_metainfo.jl | 6 +- test/test_metainfo_variabletimestep.jl | 6 +- test/test_replace_comp.jl | 6 +- test/test_variables_model_instance.jl | 2 +- 11 files changed, 83 insertions(+), 119 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index c221b654a..834b6729b 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -63,10 +63,10 @@ Instantiate a component `comp_def` in the model `md` and its variables (but not parameters). Return the resulting ComponentInstanceVariables. """ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) - comp_name = name(comp_def) + comp_name = nameof(comp_def) var_defs = variables(comp_def) - names = ([name(vdef) for vdef in var_defs]...,) + names = ([nameof(vdef) for vdef in var_defs]...,) types = Tuple{[_instance_datatype(md, vdef) for vdef in var_defs]...} values = [_instantiate_datum(md, def) for def in var_defs] @@ -113,7 +113,7 @@ end # Recursively instantiate all variables and store refs in the given dict. function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - comp_name = name(comp_def) + comp_name = nameof(comp_def) par_dict[comp_name] = Dict() if is_composite(comp_def) @@ -191,7 +191,7 @@ function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict return _combine_exported_pars(comp_def, par_dict) else - comp_name = name(comp_def) + comp_name = nameof(comp_def) d = par_dict[comp_name] pnames = Tuple(parameter_names(comp_def)) @@ -206,7 +206,7 @@ function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::D @info "_build $(comp_def.comp_id)" @info " var_dict $(var_dict)" @info " par_dict $(par_dict)" - comp_name = name(comp_def) + comp_name = nameof(comp_def) # recursive build... if is_composite(comp_def) diff --git a/src/core/connections.jl b/src/core/connections.jl index 553e76371..15d50e552 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -38,7 +38,7 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, end # Don't check sizes for ConnectorComps since they won't match. - if name(comp_def) in (:ConnectorCompVector, :ConnectorCompMatrix) + if nameof(comp_def) in (:ConnectorCompVector, :ConnectorCompMatrix) return nothing end @@ -220,7 +220,7 @@ function unconnected_params(md::ModelDef) unconnected = Vector{Tuple{Symbol,Symbol}}() for comp_def in compdefs(md) - comp_name = name(comp_def) + comp_name = nameof(comp_def) params = parameter_names(comp_def) connected = connected_params(md, comp_name) append!(unconnected, map(x->(comp_name, x), setdiff(params, connected))) @@ -482,7 +482,7 @@ function add_connector_comps(md::ModelDef) conns = internal_param_conns(md) for comp_def in compdefs(md) - comp_name = name(comp_def) + comp_name = nameof(comp_def) # first need to see if we need to add any connector components for this component internal_conns = filter(x -> x.dst_comp_name == comp_name, conns) diff --git a/src/core/defs.jl b/src/core/defs.jl index b9206d9c4..54b9da2ca 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -6,7 +6,7 @@ compdefs() = collect(values(_compdefs)) compdef(comp_id::ComponentId) = _compdefs[comp_id] function compdef(comp_name::Symbol) - matches = collect(Iterators.filter(obj -> name(obj) == comp_name, values(_compdefs))) + matches = collect(Iterators.filter(obj -> nameof(obj) == comp_name, values(_compdefs))) count = length(matches) if count == 1 @@ -18,37 +18,25 @@ function compdef(comp_name::Symbol) end end -# TBD: Might need an option like `deep=True` to recursively descend through composites -compdefs(subcomps::SubcompsDef) = values(subcomps.comps_dict) -compkeys(subcomps::SubcompsDef) = keys(subcomps.comps_dict) -hascomp(subcomps::SubcompsDef, comp_name::Symbol) = haskey(subcomps.comps_dict, comp_name) -compdef(subcomps::SubcompsDef, comp_name::Symbol) = subcomps.comps_dict[comp_name] - -@delegate compdefs(c::CompositeComponentDef) => subcomps -@delegate compkeys(c::CompositeComponentDef) => subcomps -@delegate hascomp(c::CompositeComponentDef, comp_name::Symbol) => subcomps -@delegate compdef(c::CompositeComponentDef, comp_name::Symbol) => subcomps +# TBD: @method supports ModelDef as subclass of CompositeComponentDef, otherwise not needed +@method compdefs(c::CompositeComponentDef) = values(c.comps_dict) +@method compkeys(c::CompositeComponentDef) = keys(c.comps_dict) +@method hascomp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) +@method compdef(c::CompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] +# TBD: delegation not needed if ModelDef is a subclass of CompositeComponentDef @delegate compdefs(md::ModelDef) => ccd @delegate compkeys(md::ModelDef) => ccd @delegate hascomp(md::ModelDef, comp_name::Symbol) => ccd @delegate compdef(md::ModelDef, comp_name::Symbol) => ccd -#= -Using new macro -@delegates(compdefs(subcomps::SubcompsDef) = values(subcomps.comps_dict), - ccd::CompositeComponentDef, md::ModelDef) - -@delegates(compkeys(subcomps::SubcompsDef) = keys(subcomps.comps_dict), - ccd::CompositeComponentDef, md::ModelDef) - -@delegates(hascomp(subcomps::SubcompsDef, comp_name::Symbol) = haskey(subcomps.comps_dict, comp_name), - ccd::CompositeComponentDef, md::ModelDef) - -@delegates(compdef(subcomps::SubcompsDef, comp_name::Symbol) = subcomps.comps_dict[comp_name], - ccd::CompositeComponentDef, md::ModelDef) -=# +# Return the module object for the component was defined in +compmodule(comp_id::ComponentId) = comp_id.module_name +compname(comp_id::ComponentId) = comp_id.comp_name +# TBD: needed for composites? +@delegate compname(obj::ComponentDef) => comp_id +@delegate compmodule(obj::ComponentDef) => comp_id function reset_compdefs(reload_builtins=true) empty!(_compdefs) @@ -59,8 +47,8 @@ function reset_compdefs(reload_builtins=true) end end -first_period(comp_def::LeafComponentDef) = comp_def.first -last_period(comp_def::LeafComponentDef) = comp_def.last +first_period(comp_def::ComponentDef) = comp_def.first +last_period(comp_def::ComponentDef) = comp_def.last function first_period(comp_def::CompositeComponentDef) subcomps = compdefs(comp_def) @@ -86,61 +74,40 @@ function last_period(comp_def::CompositeComponentDef) nothing # use model's last period end -# TBD: @method with comp_def first, or use comp_def::supertype(ComponentDef) -function first_period(md::ModelDef, comp_def::ComponentDef) +function first_period(md::ModelDef, comp_def::absclass(ComponentDef)) period = first_period(comp_def) return period === nothing ? time_labels(md)[1] : period end -# TBD: @method with comp_def first, or use comp_def::supertype(ComponentDef) -function last_period(md::ModelDef, comp_def::ComponentDef) +function last_period(md::ModelDef, comp_def::absclass(ComponentDef)) period = last_period(comp_def) return period === nothing ? time_labels(md)[end] : period end -# Return the module object for the component was defined in -compmodule(comp_id::ComponentId) = comp_id.module_name - -compname(comp_id::ComponentId) = comp_id.comp_name - -# TBD: @method name(obj::DatumReference) = obj.datum_name -name(dr::DatumReference) = dr.datum_name - @delegate compname(dr::DatumReference) => comp_id @delegate compmodule(dr::DatumReference) => comp_id -is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), name(dr)) -is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), name(dr)) +is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), nameof(dr)) +is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), nameof(dr)) function Base.show(io::IO, comp_id::ComponentId) print(io, "") end -""" - name(def::NamedDef) = def.name - -Return the name of `def`. Possible `NamedDef`s include `DatumDef`, `LeavComponentDef`, -`CompositeComponentDef`, and `DimensionDef`. -""" -name(def::NamedDef) = def.name - number_type(md::ModelDef) = md.number_type @delegate numcomponents(md::ModelDef) => ccd -numcomponents(comp_def::LeafComponentDef) = 0 # no subcomponents -numcomponents(comp_def::CompositeComponentDef) = numcomponents(comp_def.subcomps) # TBD: redefine - -# TBD: delete -numcomponents(info::SubcompsDef) = length(info.comps_dict) +numcomponents(obj::ComponentDef) = 0 # no subcomponents +numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) function dump_components() for comp in compdefs() - println("\n$(name(comp))") + println("\n$(nameof(comp))") for (tag, objs) in ((:Variables, variables(comp)), (:Parameters, parameters(comp)), (:Dimensions, dimensions(comp))) println(" $tag") for obj in objs - println(" $(obj.name) = $obj") + println(" $(nameof(obj)) = $obj") end end end @@ -306,14 +273,14 @@ function reset_run_periods!(md, first, last) last_per = last_period(comp_def) if first_per !== nothing && first_per < first - @warn "Resetting $(comp_def.name) component's first timestep to $first" + @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" changed = true else first = first_per end if last_per !== nothing && last_per > last - @warn "Resetting $(comp_def.name) component's last timestep to $last" + @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" changed = true else last = last_per @@ -378,12 +345,13 @@ end # Parameters # +@method external_params(obj::CompositeComponentDef) = obj.external_params + +# TBD: Delete if ModelDef subclasses CompositeComponentDef @delegate external_params(md::ModelDef) => ccd -@delegate external_params(ccd::CompositeComponentDef) => subcomps -external_params(subcomps::SubcompsDef) = subcomps.external_params -function addparameter(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit, default) - p = DatumDef(name, datatype, dimensions, description, unit, :parameter, default) +function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) + p = ParameterDef(name, datatype, dimensions, description, unit, :parameter, default) comp_def.parameters[name] = p return p end @@ -393,21 +361,21 @@ function addparameter(comp_id::ComponentId, name, datatype, dimensions, descript end """ - parameters(comp_def::LeafComponentDef) + parameters(comp_def::ComponentDef) Return a list of the parameter definitions for `comp_def`. """ -parameters(comp_def::LeafComponentDef) = values(comp_def.parameters) +parameters(obj::ComponentDef) = values(obj.parameters) function parameters(ccd::CompositeComponentDef) pars = ccd.parameters # return cached parameters, if any if length(pars) == 0 - for (dr, name) in ccd.subcomps.exports + for (dr, name) in ccd.exports cd = compdef(dr.comp_id) - if has_parameter(cd, dr.datum_name) - pars[name] = parameter(cd, dr.datum_name) + if has_parameter(cd, nameof(dr)) + pars[name] = parameter(cd, nameof(dr)) end end end @@ -433,14 +401,14 @@ Return a list of all parameter names for a given component `comp_name` in a mode """ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) -#parameter_names(comp_def::ComponentDef) = [name(param) for param in parameters(comp_def)] +#parameter_names(comp_def::ComponentDef) = [nameof(param) for param in parameters(comp_def)] parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) @delegate parameter(md::ModelDef, param_name::Symbol) => ccd -parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), dr.datum_name) +parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), nameof(dr)) function parameter(cd::ComponentDef, name::Symbol) if is_composite(cd) @@ -450,7 +418,7 @@ function parameter(cd::ComponentDef, name::Symbol) try return cd.parameters[name] catch - error("Parameter $name was not found in component $(cd.name)") + error("Parameter $name was not found in component $(nameof(cd))") end end @@ -543,7 +511,7 @@ function variables(ccd::CompositeComponentDef) # return cached variables, if any if length(vars) == 0 - for (dr, name) in ccd.subcomps.exports + for (dr, name) in ccd.exports cd = compdef(dr.comp_id) if has_variable(cd, dr.datum_name) vars[name] = variable(cd, dr.datum_name) @@ -587,7 +555,7 @@ Return a list of all variable names for a given component `comp_name` in a model """ variable_names(md::ModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) -variable_names(comp_def::ComponentDef) = [name(var) for var in variables(comp_def)] +variable_names(comp_def::ComponentDef) = [nameof(var) for var in variables(comp_def)] function variable_unit(md::ModelDef, comp_name::Symbol, var_name::Symbol) @@ -616,7 +584,7 @@ Add all exported variables to a CompositeComponentDef. function addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) # TBD: this needs attention for (dr, exp_name) in exports - addvariable(comp_def, variable(comp_def, name(variable)), exp_name) + addvariable(comp_def, variable(comp_def, nameof(variable)), exp_name) end end @@ -656,13 +624,12 @@ end const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} -@delegate _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::ComponentDef) => ccd -@delegate _append_comp!(ccd::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) => subcomps - -function _append_comp!(subcomps::SubcompsDef, comp_name::Symbol, comp_def::ComponentDef) - subcomps.comps_dict[comp_name] = comp_def +function _append_comp!(ccd::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) + ccd.comps_dict[comp_name] = comp_def end +@delegate _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::ComponentDef) => ccd + """ add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) @@ -744,7 +711,7 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; # Set parameters to any specified defaults for param in parameters(comp_def) if param.default !== nothing - set_param!(md, comp_name, name(param), param.default) + set_param!(md, comp_name, nameof(param), param.default) end end @@ -866,10 +833,6 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com add_comp!(md, comp_id, comp_name; first=first, last=last, before=before, after=after) end -# -# TBD: we can probably remove most of this copying code and just rely on deepcopy(). -# - """ copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) diff --git a/src/core/instances.jl b/src/core/instances.jl index 94099fefa..ab5e01161 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -21,11 +21,11 @@ has_component(subcomps::SubcompsInstance, name::Symbol) = haskey(subcomps.comps_ compdef(ci::ComponentInstance) = compdef(ci.comp_id) """ - name(ci::ComponentInstance) + nameof(ci::ComponentInstance) Return the name of the component `ci`. """ -name(ci::ComponentInstance) = ci.comp_name +nameof(ci::ComponentInstance) = ci.comp_name compid(ci::ComponentInstance) = ci.comp_id dims(ci::ComponentInstance) = ci.dim_dict @@ -69,7 +69,7 @@ components, and add the `first` and `last` of `mi` to the ends of the `firsts` a @delegate add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) => subcomps function add_comp!(subcomps::SubcompsInstance, ci::ComponentInstance) - subcomps.comps_dict[name(ci)] = ci + subcomps.comps_dict[nameof(ci)] = ci push!(subcomps.firsts, first_period(ci)) push!(subcomps.lasts, last_period(ci)) diff --git a/src/core/types.jl b/src/core/types.jl index 823425efa..3594b4480 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -286,7 +286,7 @@ mutable struct SubcompsDef <: SubcompsDefSuper bindings::Vector{Pair{DatumReference, BindingTypes}}, exports::Vector{Pair{DatumReference, Symbol}}) self = new() - self.comps_dict = OrderedDict{Symbol, ComponentDef}([name(cd) => cd for cd in comps]) + self.comps_dict = OrderedDict{Symbol, ComponentDef}([nameof(cd) => cd for cd in comps]) self.bindings = bindings self.exports = exports self.internal_param_conns = Vector{InternalParameterConnection}() @@ -406,7 +406,7 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst subcomps::T function ComponentInstance{T, TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def); subcomps::T=nothing) where + name::Symbol=nameof(comp_def); subcomps::T=nothing) where {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = new{T, TV, TP}() @@ -443,7 +443,7 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst end function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def); subcomps::T=nothing) where + name::Symbol=nameof(comp_def); subcomps::T=nothing) where {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} ComponentInstance{T, TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) end diff --git a/src/core/types2.jl b/src/core/types2.jl index b07b62d5a..0b26cdda9 100644 --- a/src/core/types2.jl +++ b/src/core/types2.jl @@ -183,16 +183,17 @@ ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) name::Symbol end -@method name(obj::NamedObj) = obj.name +""" + nameof(obj::NamedDef) = obj.name + +Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, +`CompositeComponentDef`, `DatumReference` and `DimensionDef`. +""" +@method Base.nameof(obj::NamedObj) = obj.name -# TBD -# @class DatumReference <: NamedObj -# comp_id::ComponentId -# end # Stores references to the name of a component variable or parameter -struct DatumReference +@class DatumReference <: NamedObj comp_id::ComponentId - datum_name::Symbol end # *Def implementation doesn't need to be performance-optimized since these @@ -275,7 +276,7 @@ end # superclass initialization # ComponentDef(self, comp_id, comp_name) - self.comps_dict = OrderedDict{Symbol, T}([name(cd) => cd for cd in comps]) + self.comps_dict = OrderedDict{Symbol, T}([nameof(cd) => cd for cd in comps]) self.bindings = bindings self.exports = exports self.internal_param_conns = Vector{InternalParameterConnection}() @@ -369,7 +370,7 @@ end run_timestep::Union{Nothing, Function} function ComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where + name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = new{TV, TP}() @@ -405,7 +406,7 @@ end end function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=name(comp_def)) where + name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) end diff --git a/src/utils/graph.jl b/src/utils/graph.jl index f25860596..2ad93bffa 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -27,7 +27,7 @@ function show(io::IO, m::Model) for (i, comp_name) in enumerate(compkeys(m.md)) comp_def = compdef(m.md, comp_name) - println(io, "$i. $(comp_def.comp_id) as :$(comp_def.name)") + println(io, "$i. $(comp_def.comp_id) as :$(nameof(comp_def))") _show_conns(io, m, comp_name, :incoming) _show_conns(io, m, comp_name, :outgoing) end @@ -49,7 +49,7 @@ end function get_connections(m::Model, ci::ComponentInstance, which::Symbol) if is_leaf(ci) - return get_connections(m, name(ci.comp), which) + return get_connections(m, nameof(ci.comp), which) end conns = [] diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index a30707a05..0bf0b7033 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -43,9 +43,9 @@ c2 = compdef(test_model, :ch4forcing2) @test c1 == compdef(:ch4forcing1) @test_throws ErrorException compdef(:missingcomp) -@test c2.comp_id.module_name == :TestMetaInfo -@test c2.comp_id.comp_name == :ch4forcing1 -@test c2.name == :ch4forcing2 +@test compmodule(c2) == :TestMetaInfo +@test compname(c2) == :ch4forcing1 +@test nameof(c2) == :ch4forcing2 vars = Mimi.variable_names(c2) @test length(vars) == 3 diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index baebe2954..2503ba86e 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -43,9 +43,9 @@ c2 = compdef(test_model, :ch4forcing2) @test c1 == compdef(:ch4forcing1) @test_throws ErrorException compdef(:missingcomp) -@test c2.comp_id.module_name == :TestMetaInfo_VariableTimestep -@test c2.comp_id.comp_name == :ch4forcing1 -@test c2.name == :ch4forcing2 +@test compmodule(c2) == :TestMetaInfo_VariableTimestep +@test compname(c2) == :ch4forcing1 +@test nameof(c2) == :ch4forcing2 vars = Mimi.variable_names(c2) @test length(vars) == 3 diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 5b053c78f..01e24534c 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -66,7 +66,7 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection with a parameter with a time dimension @test_throws ErrorException replace_comp!(m, bad1, :second) # Cannot make reconnections because :x in bad1 has different dimensions replace_comp!(m, bad1, :second, reconnect = false) # Can replace without reconnecting -@test name(compdef(m.md, :second)) == :bad1 # Successfully replaced +@test nameof(compdef(m.md, :second)) == :bad1 # Successfully replaced # 3. Test bad internal outgoing variable @@ -78,7 +78,7 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection from a variable with a time dimension @test_throws ErrorException replace_comp!(m, bad2, :first) # Cannot make reconnections because bad2 does not have a variable :y replace_comp!(m, bad2, :first, reconnect = false) # Can replace without reconnecting -@test name(compdef(m.md, :first)) == :bad2 # Successfully replaced +@test nameof(compdef(m.md, :first)) == :bad2 # Successfully replaced # 4. Test bad external parameter name @@ -91,7 +91,7 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs (:warn, r".*parameter x no longer exists in component.*") replace_comp!(m, bad3, :X) -@test name(compdef(m.md, :X)) == :bad3 # The replacement was still successful +@test nameof(compdef(m.md, :X)) == :bad3 # The replacement was still successful #external_param_conns(md, comp_name) @test length(external_param_conns(m.md)) == 0 # The external paramter connection was removed @test length(external_params(m.md)) == 1 # The external parameter still exists diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index c61222565..efc020759 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -44,7 +44,7 @@ citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md @test typeof(ci) <: ComponentInstance && ci == compinstance(mi, :testcomp1) @test typeof(cdef) <: ComponentDef && cdef == compdef(ci.comp_id) -@test name(ci) == :testcomp1 +@test nameof(ci) == :testcomp1 @test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: ComponentInstance #test convenience functions that can be called with name symbol From 0bac195b060c0553afe0d29bfbaf1cdae712659b Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 3 Dec 2018 14:39:30 -0800 Subject: [PATCH 20/81] Add type params to ComponentInstance --- src/core/types2.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/types2.jl b/src/core/types2.jl index 0b26cdda9..06258eef3 100644 --- a/src/core/types2.jl +++ b/src/core/types2.jl @@ -192,7 +192,7 @@ Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, @method Base.nameof(obj::NamedObj) = obj.name # Stores references to the name of a component variable or parameter -@class DatumReference <: NamedObj +@class DatumReference <: NamedObj begin comp_id::ComponentId end @@ -411,15 +411,16 @@ function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) end -@class mutable CompositeInstance <: ComponentInstance begin +@class mutable CompositeInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: ComponentInstance begin comps_dict::OrderedDict{Symbol, _ComponentInstance_} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Vector{Clock} - function CompositeInstance(comps::Vector{T}) where {T <: _ComponentInstance_} + function CompositeInstance{TV, TP}(comps::Vector{T}) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters, T <: _ComponentInstance_} self = new() - self.comps_dict = OrderedDict{Symbol, _ComponentInstance_}([ci.comp_name => ci for ci in comps]) + self.comps_dict = OrderedDict{Symbol, T}([ci.comp_name => ci for ci in comps]) self.firsts = Vector{Int}() self.lasts = Vector{Int}() self.clocks = Vector{Clock}() @@ -453,9 +454,9 @@ types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).paramet # ModelInstance holds the built model that is ready to be run mutable struct ModelInstance md::ModelDef - cci::Union{Nothing, CompositeComponentInstance} + cci::Union{Nothing, CompositeInstance} - function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) + function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeInstance}=nothing) return new(md, cci) end end From cd7a3fd89c1e5c620604479f56067960702a9837 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 6 Dec 2018 16:41:56 -0800 Subject: [PATCH 21/81] WIP - Checkpoint --- src/Mimi.jl | 4 +- src/core/build.jl | 27 +- src/core/connections.jl | 15 +- src/core/defcomposite.jl | 5 +- src/core/defs.jl | 14 +- src/core/instances.jl | 29 +-- src/core/types.jl | 385 +++++++++++++++------------- src/core/types2.jl | 533 --------------------------------------- test/test_composite.jl | 7 +- 9 files changed, 249 insertions(+), 770 deletions(-) delete mode 100644 src/core/types2.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index 7ee0fdb11..7afc21819 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -42,9 +42,7 @@ export update_params!, variables -# TBD: testing new @classes in classes.jl -include("core/types2.jl") - +include("core/types.jl") include("core/delegate.jl") # After loading types and delegation macro, the rest is alphabetical diff --git a/src/core/build.jl b/src/core/build.jl index 834b6729b..753c638ab 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -78,7 +78,7 @@ function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{ names = [] values = [] - for (dr, name) in comp_def.subcomps.exports + for (dr, name) in comp_def.exports if is_variable(dr) obj = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? value = getproperty(obj, dr.datum_name) @@ -95,7 +95,7 @@ function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{ names = [] values = [] - for (dr, name) in comp_def.subcomps.exports + for (dr, name) in comp_def.exports if is_parameter(dr) d = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? value = d[dr.datum_name] @@ -208,19 +208,25 @@ function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::D @info " par_dict $(par_dict)" comp_name = nameof(comp_def) - # recursive build... - if is_composite(comp_def) - comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def.subcomps)] - subcomps = SubcompsInstance(comps) - else - subcomps = nothing - end + pars = _instantiate_params(comp_def, par_dict) + vars = var_dict[comp_name] + return ComponentInstance(comp_def, vars, pars, comp_name) +end + +function _build(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + @info "_build composite $(comp_def.comp_id)" + @info " var_dict $(var_dict)" + @info " par_dict $(par_dict)" + comp_name = nameof(comp_def) + + built_comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def)] pars = _instantiate_params(comp_def, par_dict) vars = var_dict[comp_name] - return ComponentInstance(comp_def, vars, pars, comp_name, subcomps=subcomps) + return CompositeInstance(comp_def, vars, pars, comp_name) end + function _build(md::ModelDef) add_connector_comps(md) @@ -231,6 +237,7 @@ function _build(md::ModelDef) msg = "Cannot build model; the following parameters are not set: $params" error(msg) end + var_dict = Dict{Symbol, Any}() # collect all var defs and par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go diff --git a/src/core/connections.jl b/src/core/connections.jl index 15d50e552..f358e37f2 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -63,7 +63,7 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, end @delegate backups(md::ModelDef) => ccd -backups(ccd::CompositeComponentDef) = ccd.subcomps.backups +backups(obj::CompositeComponentDef) = obj.backups @delegate add_backup!(md::ModelDef, obj) => ccd add_backup!(ccd::CompositeComponentDef, obj) = push!(ccd.backups, obj) @@ -255,8 +255,6 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T nothing end -internal_param_conns(subcomps::SubcompsDef) = subcomps.internal_param_conns -@delegate internal_param_conns(ccd::CompositeComponentDef) => subcomps @delegate internal_param_conns(md::ModelDef) => ccd # Find internal param conns to a given destination component @@ -264,8 +262,7 @@ function internal_param_conns(md::ModelDef, dst_comp_name::Symbol) return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(md)) end -external_param_conns(subcomps::SubcompsDef) = subcomps.external_param_conns -@delegate external_param_conns(ccd::CompositeComponentDef) => subcomps +external_param_conns(ccd::CompositeComponentDef) = ccd.external_param_conns @delegate external_param_conns(md::ModelDef) => ccd # Find external param conns for a given comp @@ -277,7 +274,7 @@ end function external_param(ccd::CompositeComponentDef, name::Symbol) try - return ccd.subcomps.external_params[name] + return ccd.external_params[name] catch err if err isa KeyError error("$name not found in external parameter list") @@ -290,19 +287,19 @@ end @delegate add_internal_param_conn!(md::ModelDef, conn::InternalParameterConnection) => ccd function add_internal_param_conn!(ccd::CompositeComponentDef, conn::InternalParameterConnection) - push!(ccd.subcomps.internal_param_conns, conn) + push!(ccd.internal_param_conns, conn) end @delegate add_external_param_conn!(md::ModelDef, conn::ExternalParameterConnection) => ccd function add_external_param_conn!(ccd::CompositeComponentDef, conn::ExternalParameterConnection) - push!(ccd.subcomps.external_param_conns, conn) + push!(ccd.external_param_conns, conn) end @delegate set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter) => ccd function set_external_param!(ccd::CompositeComponentDef, name::Symbol, value::ModelParameter) - ccd.subcomps.external_params[name] = value + ccd.external_params[name] = value end function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 50ae3f97c..59024590e 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -145,10 +145,7 @@ macro defcomposite(cc_name, ex) # name = (alias === nothing ? comp_name : alias) # expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) - expr = :(info = SubcompsDef($comps, bindings=$bindings, exports=$exports)) - addexpr(expr) - - expr = :(ComponentDef($comp_id, $comp_name; component_info=info)) + expr = :(CompositeComponentDef($comp_id, $comp_name, $comps; bindings=$bindings, exports=$exports)) addexpr(expr) end end diff --git a/src/core/defs.jl b/src/core/defs.jl index 54b9da2ca..81146c4ed 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -51,10 +51,8 @@ first_period(comp_def::ComponentDef) = comp_def.first last_period(comp_def::ComponentDef) = comp_def.last function first_period(comp_def::CompositeComponentDef) - subcomps = compdefs(comp_def) - - if numcomponents(subcomps) > 0 - firsts = [first_period(c) for c in subcomps] + if numcomponents(comp_def) > 0 + firsts = [first_period(c) for c in comp_def] if findfirst(isequal(nothing), firsts) == nothing # i.e., there are no `nothing`s return min(Vector{Int}(firsts)...) end @@ -63,10 +61,8 @@ function first_period(comp_def::CompositeComponentDef) end function last_period(comp_def::CompositeComponentDef) - subcomps = compdefs(comp_def) - - if numcomponents(subcomps) > 0 - lasts = [last_period(cd) for cd in subcomps] + if numcomponents(comp_def) > 0 + lasts = [last_period(c) for c in comp_def] if findfirst(isequal(nothing), lasts) == nothing # i.e., there are no `nothing`s return max(Vector{Int}(lasts)...) end @@ -98,7 +94,7 @@ number_type(md::ModelDef) = md.number_type @delegate numcomponents(md::ModelDef) => ccd -numcomponents(obj::ComponentDef) = 0 # no subcomponents +numcomponents(obj::ComponentDef) = 0 # no sub-components numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) function dump_components() diff --git a/src/core/instances.jl b/src/core/instances.jl index ab5e01161..ae9e154e4 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -9,12 +9,10 @@ Return the `ModelDef` contained by ModelInstance `mi`. """ modeldef(mi::ModelInstance) = mi.md -compinstance(SubcompsInstance, name::Symbol) = subcomps.comps_dict[name] -@delegate compinstance(cci::CompositeComponentInstance, name::Symbol) => subcomps +compinstance(cci::CompositeComponentInstance, name::Symbol) = cci.comps_dict[name] @delegate compinstance(mi::ModelInstance, name::Symbol) => cci -has_component(subcomps::SubcompsInstance, name::Symbol) = haskey(subcomps.comps_dict, name) -@delegate has_component(ci::CompositeComponentInstance, name::Symbol) => subcomps +has_component(cci::CompositeComponentInstance, name::Symbol) = haskey(cci.comps_dict, name) @delegate has_component(mi::ModelInstance, name::Symbol) => cci @@ -38,22 +36,17 @@ last_period(ci::ComponentInstance) = ci.last Return an iterator over components in model instance `mi`. """ @delegate components(mi::ModelInstance) => cci +components(cci::CompositeComponentInstance) = values(cci.comps_dict) -@delegate components(ci::CompositeComponentInstance) => subcomps -components(subcomps::SubcompsInstance) = values(subcomps.comps_dict) - -firsts(subcomps::SubcompsInstance) = subcomps.firsts -@delegate firsts(cci::CompositeComponentInstance) => subcomps +firsts(cci::CompositeComponentInstance) = cci.firsts @delegate firsts(mi::ModelInstance) => cci @delegate firsts(m::Model) => mi -lasts(subcomps::SubcompsInstance) = subcomps.lasts -@delegate lasts(cci::CompositeComponentInstance) => subcomps +lasts(cci::CompositeComponentInstance) = cci.lasts @delegate lasts(mi::ModelInstance) => cci @delegate lasts(m::Model) => mi -clocks(subcomps::SubcompsInstance) = subcomps.clocks -@delegate clocks(cci::CompositeComponentInstance) => subcomps +clocks(cci::CompositeComponentInstance) = cci.clocks @delegate clocks(mi::ModelInstance) => cci @delegate clocks(m::Model) => mi @@ -66,13 +59,11 @@ components, and add the `first` and `last` of `mi` to the ends of the `firsts` a """ @delegate add_comp!(mi::ModelInstance, ci::ComponentInstance) => cci -@delegate add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) => subcomps - -function add_comp!(subcomps::SubcompsInstance, ci::ComponentInstance) - subcomps.comps_dict[nameof(ci)] = ci +function add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) + cci.comps_dict[nameof(ci)] = ci - push!(subcomps.firsts, first_period(ci)) - push!(subcomps.lasts, last_period(ci)) + push!(cci.firsts, first_period(ci)) + push!(cci.lasts, last_period(ci)) end # diff --git a/src/core/types.jl b/src/core/types.jl index 3594b4480..607083aad 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,3 +1,6 @@ +using Classes +using DataStructures + # # 1. Types supporting parameterized Timestep and Clock objects # @@ -170,46 +173,27 @@ end ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) -# Indicates that the object has a `name` attribute -abstract type NamedDef end - -# Supertype for vars and params -# abstract type DatumDef <: NamedDef end +# +# TBD: consider a naming protocol that adds Cls to class struct names +# so it's obvious in the code. +# -# The same structure is used for variables and parameters -mutable struct DatumDef <: NamedDef +# Objects with a `name` attribute +@class NamedObj begin name::Symbol - datatype::DataType - dimensions::Vector{Symbol} - description::String - unit::String - datum_type::Symbol # :parameter or :variable - default::Any # used only for Parameters - - function DatumDef(name::Symbol, datatype::DataType, dimensions::Vector{Symbol}, - description::String, unit::String, datum_type::Symbol, - default::Any=nothing) - self = new() - self.name = name - self.datatype = datatype - self.dimensions = dimensions - self.description = description - self.unit = unit - self.datum_type = datum_type - self.default = default - return self - end - end -struct DimensionDef <: NamedDef - name::Symbol -end +""" + nameof(obj::NamedDef) = obj.name + +Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, +`CompositeComponentDef`, `DatumReference` and `DimensionDef`. +""" +@method Base.nameof(obj::NamedObj) = obj.name # Stores references to the name of a component variable or parameter -struct DatumReference +@class DatumReference <: NamedObj begin comp_id::ComponentId - datum_name::Symbol end # *Def implementation doesn't need to be performance-optimized since these @@ -221,55 +205,64 @@ end global const BindingTypes = Union{Int, Float64, DatumReference} -# Abstract type serves as a sort of forward declaration that permits definition -# of interdependent types ComponentDef and SubcompsDef. -abstract type SubcompsDefSuper end -global const SubcompsDefTypes = Union{Nothing, SubcompsDefSuper} +@class DimensionDef <: NamedObj + +# Similar structure is used for variables and parameters (parameters merely adds `default`) +@class mutable DatumDef <: NamedObj begin + datatype::DataType + dimensions::Vector{Symbol} + description::String + unit::String +end + +@class mutable VariableDef <: DatumDef -mutable struct ComponentDef{T <: SubcompsDefTypes} <: NamedDef +@class mutable ParameterDef <: DatumDef begin + # ParameterDef adds a default value, which can be specified in @defcomp + default::Any +end + +@class mutable ComponentDef <: NamedObj begin comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) - name::Symbol # Union{Nothing, Symbol} ? - variables::OrderedDict{Symbol, DatumDef} - parameters::OrderedDict{Symbol, DatumDef} + variables::OrderedDict{Symbol, VariableDef} + parameters::OrderedDict{Symbol, ParameterDef} dimensions::OrderedDict{Symbol, DimensionDef} first::Union{Nothing, Int} last::Union{Nothing, Int} is_uniform::Bool - # info about sub-components, or nothing - subcomps::T + function ComponentDef(self::ComponentDef, comp_id::Nothing) + error("Leaf ComponentDef objects must have a valid ComponentId name (not nothing)") + end # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef{T}(comp_id::Union{Nothing, ComponentId}, - comp_name::Symbol=comp_id.comp_name, - subcomps::T=nothing) where {T <: SubcompsDefTypes} - self = new{T}() - - if (subcomps === nothing && comp_id === nothing) - error("Leaf ComponentDef instances must have a Symbol name (not nothing)") + function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}=nothing; + name::Union{Nothing, Symbol}=nothing) + if name === nothing + name = (comp_id === nothing ? gensym("anonymous") : comp_id.comp_name) end + NamedObj(self, name) self.comp_id = comp_id - self.name = comp_name - self.variables = OrderedDict{Symbol, DatumDef}() - self.parameters = OrderedDict{Symbol, DatumDef}() + self.variables = OrderedDict{Symbol, VariableDef}() + self.parameters = OrderedDict{Symbol, ParameterDef}() self.dimensions = OrderedDict{Symbol, DimensionDef}() self.first = self.last = nothing self.is_uniform = true - self.subcomps = subcomps return self - end - # Syntactic sugar so caller doesn't have to specify {SubcompsDef} - function ComponentDef(comp_id::Union{Nothing, ComponentId}, name::Symbol, subcomps::T) where {T <: SubcompsDefSuper} - ComponentDef{T}(comp_id, name, subcomps) + return ComponentDef(comp_id, name=name) end - ComponentDef() = ComponentDef(nothing, gensym("anonymous"), SubcompsDef()) + function ComponentDef(comp_id::Union{Nothing, ComponentId}; + name::Union{Nothing, Symbol}=nothing) + self = new() + return ComponentDef(self, comp_id, name=name) + end end -mutable struct SubcompsDef <: SubcompsDefSuper - comps_dict::OrderedDict{Symbol, ComponentDef} +@class mutable CompositeComponentDef <: ComponentDef begin + comps_dict::OrderedDict{Symbol, _ComponentDef_} bindings::Vector{Pair{DatumReference, BindingTypes}} exports::Vector{Pair{DatumReference, Symbol}} @@ -282,50 +275,57 @@ mutable struct SubcompsDef <: SubcompsDefSuper sorted_comps::Union{Nothing, Vector{Symbol}} - function SubcompsDef(comps::Vector{ComponentDef}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) - self = new() - self.comps_dict = OrderedDict{Symbol, ComponentDef}([nameof(cd) => cd for cd in comps]) - self.bindings = bindings - self.exports = exports - self.internal_param_conns = Vector{InternalParameterConnection}() - self.external_param_conns = Vector{ExternalParameterConnection}() - self.external_params = Dict{Symbol, ModelParameter}() - self.backups = Vector{Symbol}() - self.sorted_comps = nothing - + function CompositeComponentDef(self::_CompositeComponentDef_, + comp_id::ComponentId, comps::Vector{T}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} + + comps_dict = OrderedDict{Symbol, T}([nameof(cd) => cd for cd in comps]) + in_conns = Vector{InternalParameterConnection}() + ex_conns = Vector{ExternalParameterConnection}() + ex_params = Dict{Symbol, ModelParameter}() + backups = Vector{Symbol}() + sorted_comps = nothing + + ComponentDef(self, comp_id) # superclass init [TBD: allow for alternate comp_name?] + CompositeComponentDef(self, comps_dict, bindings, exports, in_conns, ex_conns, + ex_params, backups, sorted_comps) return self end - function SubcompsDef() - comps = Vector{ComponentDef}() + function CompositeComponentDef(comp_id::ComponentId, comps::Vector{T}, + bindings::Vector{Pair{DatumReference, BindingTypes}}, + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: absclass(ComponentDef)} + + self = new() + return CompositeComponentDef(self, comp_id, comps, bindings, exports) + end + + function CompositeComponentDef(self::Union{Nothing, absclass(CompositeComponentDef)}=nothing) + self = (self === nothing ? new() : self) + + comp_id = ComponentId(:anonymous, :anonymous) # TBD: pass these in? + comps = Vector{absclass(ComponentDef)}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() - return SubcompsDef(comps, bindings, exports) + return CompositeComponentDef(self, comp_id, comps, bindings, exports) end end -global const LeafComponentDef = ComponentDef{Nothing} -global const CompositeComponentDef = ComponentDef{SubcompsDef} - -is_leaf(comp::LeafComponentDef) = true -is_leaf(comp::CompositeComponentDef) = false -is_composite(comp::ComponentDef) = !is_leaf(comp) - -mutable struct ModelDef - ccd::ComponentDef - dimensions::Dict{Symbol, Dimension} # TBD: use the one in ccd instead +@class mutable ModelDef <: CompositeComponentDef begin + dimensions2::Dict{Symbol, Dimension} number_type::DataType - function ModelDef(ccd::CompositeComponentDef, number_type::DataType=Float64) + function ModelDef(self::_ModelDef_, number_type::DataType=Float64) + CompositeComponentDef(self) # call super's initializer + dimensions = Dict{Symbol, Dimension}() - return new(ccd, dimensions, number_type) + return ModelDef(self, dimensions, number_type) end function ModelDef(number_type::DataType=Float64) - # passes an anonymous top-level (composite) ComponentDef - return ModelDef(ComponentDef(), number_type) + self = new() + return ModelDef(self, number_type) end end @@ -334,64 +334,29 @@ end # # Supertype for variables and parameters in component instances -abstract type ComponentInstanceData end - -struct ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData +@class ComponentInstanceData{NT <: NamedTuple} begin nt::NT - - function ComponentInstanceParameters{NT}(nt::NT) where {NT <: NamedTuple} - return new{NT}(nt) - end end -function ComponentInstanceParameters(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceParameters{NT}(NT(values)) -end +@method nt(obj::ComponentInstanceData) = getfield(obj, :nt) +@method types(obj::ComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters +@method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) +@method Base.values(obj::ComponentInstanceData) = values(nt(obj)) -function ComponentInstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceParameters{NT}(NT(values)) -end +_make_data_obj(subclass::DataType, nt::NT) where {NT <: NamedTuple} = subclass{NT}(nt) -struct ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData - nt::NT - - function ComponentInstanceVariables{NT}(nt::NT) where {NT <: NamedTuple} - return new{NT}(nt) - end -end - -function ComponentInstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceVariables{NT}(NT(values)) -end - -function ComponentInstanceVariables(names, types, values) +function _make_data_obj(subclass::DataType, names, types, values) NT = NamedTuple{names, types} - ComponentInstanceVariables{NT}(NT(values)) + _make_data_obj(subclass, NT(values)) end -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimDict - dict::Dict{Symbol, Vector{Int}} -end +@class ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData +@class ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimDict of dimensions by name -# as the "d" parameter. -@inline function Base.getproperty(dimdict::DimDict, property::Symbol) - return getfield(dimdict, :dict)[property] -end +ComponentInstanceParameters(names, types, values) = _make_data_obj(ComponentInstanceParameters, names, types, values) +ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInstanceVariables, names, types, values) -nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) -Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) -Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) -types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters - -abstract type SubcompsInstanceSuper end -const SubcompsInstanceTypes = Union{Nothing, SubcompsInstanceSuper} - -mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin comp_name::Symbol comp_id::ComponentId variables::TV @@ -402,29 +367,29 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst init::Union{Nothing, Function} run_timestep::Union{Nothing, Function} - # info about sub-components, or nothing - subcomps::T - - function ComponentInstance{T, TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def); subcomps::T=nothing) where - {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = new{T, TV, TP}() + function ComponentInstance(self::absclass(ComponentInstance), + comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + self.comp_id = comp_id = comp_def.comp_id self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage + self.dim_dict = Dict{Symbol, Vector{Int}}() # values set in "build" stage self.variables = vars self.parameters = pars self.first = comp_def.first self.last = comp_def.last - self.subcomps = subcomps comp_module = Base.eval(Main, comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) - # All ComponentInstances use a standard method that just loops over inner components. + # CompositeComponentInstances use a standard method that just loops over inner components. # TBD: use FunctionWrapper here? function get_func(name) + if is_composite(self) + return nothing + end + func_name = Symbol("$(name)_$(self.comp_name)") try Base.eval(comp_module, func_name) @@ -435,56 +400,118 @@ mutable struct ComponentInstance{T <: SubcompsInstanceTypes, TV <: ComponentInst # `is_composite` indicates a ComponentInstance used to store summary # data for ComponentInstance and is not itself runnable. - self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") - self.init = is_composite(self) ? nothing : get_func("init") + self.init = get_func("init") + self.run_timestep = get_func("run_timestep") return self end -end -function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def); subcomps::T=nothing) where - {T <: SubcompsInstanceTypes, TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - ComponentInstance{T, TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) + function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = new{TV, TP}() + return ComponentInstance(self, comp_def, vars, pars, name) + end end -mutable struct SubcompsInstance <: SubcompsInstanceSuper - comps_dict::OrderedDict{Symbol, ComponentInstance} +# These can be called on CompositeComponentInstances and ModelInstances +@method comp_name(obj::ComponentInstance) = obj.comp_name +@method comp_id(obj::ComponentInstance) = obj.comp_id +@method variables(obj::ComponentInstance) = obj.variables +@method parameters(obj::ComponentInstance) = obj.parameters +@method dim_dict(obj::ComponentInstance) = obj.dim_dict +@method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_dict, name) +@method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_dict[name] +@method Base.first(obj::ComponentInstance) = obj.first +@method Base.last(obj::ComponentInstance) = obj.last +@method init_func(obj::ComponentInstance) = obj.init +@method run_timestep_func(obj::ComponentInstance) = obj.run_timestep + +@class mutable CompositeComponentInstance{TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} <: ComponentInstance begin + comps_dict::OrderedDict{Symbol, absclass(ComponentInstance)} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Vector{Clock} - - function SubcompsInstance(comps::Vector{T}) where {T <: ComponentInstance} - self = new() - self.comps_dict = OrderedDict{Symbol, ComponentInstance}([ci.comp_name => ci for ci in comps]) - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - self.clocks = Vector{Clock}() + + function CompositeComponentInstance(self::absclass(CompositeComponentInstance), + comps::Vector{absclass(ComponentInstance)}, + comp_def::ComponentDef, name::Symbol=nameof(comp_def)) + comps_dict = OrderedDict{Symbol, absclass(ComponentInstance)}() + firsts = Vector{Int}() + lasts = Vector{Int}() + clocks = Vector{Clock}() + + for ci in comps + comps_dict[ci.comp_name] = ci + push!(firsts, ci.first) + push!(lasts, ci.last) + # push!(clocks, ?) + end + + (vars, pars) = _collect_vars_pars(comps) + ComponentInstance(self, comp_def, vars, pars, name) + CompositeComponentInstance(self, comps_dict, firsts, lasts, clocks) return self end + + # Constructs types of vars and params from sub-components + function CompositeComponentInstance(comps::Vector{absclass(ComponentInstance)}, + comp_def::ComponentDef, + name::Symbol=nameof(comp_def)) + (TV, TP) = _comp_instance_types(comps) + self = new{TV, TP}() + CompositeComponentInstance(self, comps, comp_def, name) + end +end + +# These methods can be called on ModelInstances as well +@method comp_dict(obj::CompositeComponentInstance) = obj.comp_dict +@method has_comp(obj::CompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) +@method comp_instance(obj::CompositeComponentInstance, name::Symbol) = obj.comps_dict[name] +@method firsts(obj::CompositeComponentInstance) = obj.firsts +@method lasts(obj::CompositeComponentInstance) = obj.lasts +@method clocks(obj::CompositeComponentInstance) = obj.clocks + +@method is_leaf(ci::ComponentInstance) = true +@method is_leaf(ci::CompositeComponentInstance) = false +@method is_composite(ci::ComponentInstance) = !is_leaf(ci) + +# TBD: write these +function _comp_instance_types(comps::Vector{absclass(ComponentInstance)}) + error("Need to define comp_instance_types") end -const LeafComponentInstance{TV, TP} = ComponentInstance{Nothing, TV, TP} where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} +function _collect_vars_pars(comps::Vector{absclass(ComponentInstance)}) + error("Need to define comp_instance_types") +end -const CompositeComponentInstance{TV, TP} = ComponentInstance{SubcompsInstance, TV, TP} where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimDict + dict::Dict{Symbol, Vector{Int}} +end -is_leaf(ci::LeafComponentInstance{TV, TP}) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} = true -is_leaf(ci::ComponentInstance) = false -is_composite(ci::ComponentInstance) = !is_leaf(ci) +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimDict of dimensions by name +# as the "d" parameter. +Base.getproperty(dimdict::DimDict, property::Symbol) = getfield(dimdict, :dict)[property] # ModelInstance holds the built model that is ready to be run -mutable struct ModelInstance +@class ModelInstance{TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} <: CompositeComponentInstance begin md::ModelDef - cci::Union{Nothing, CompositeComponentInstance} - function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeComponentInstance}=nothing) - return new(md, cci) + # similar to generated constructor, but taking TV and TP from superclass instance + function ModelInstance(md::ModelDef, s::CompositeComponentInstance{TV, TP}) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + new{TV, TP}(s.comp_name, s.comp_id, s.variables, s.parameters, s.dim_dict, s.first, s.last, + s.init, s.run_timestep, s.comps_dict, s.firsts, s.lasts, s.clocks, md) end end - # # 6. User-facing Model types providing a simplified API to model definitions and instances. # @@ -499,7 +526,7 @@ a `number_type` of `Float64`. mutable struct Model md::ModelDef mi::Union{Nothing, ModelInstance} - + function Model(number_type::DataType=Float64) return new(ModelDef(number_type), nothing) end diff --git a/src/core/types2.jl b/src/core/types2.jl deleted file mode 100644 index 06258eef3..000000000 --- a/src/core/types2.jl +++ /dev/null @@ -1,533 +0,0 @@ -using Classes -using DataStructures - -# -# 1. Types supporting parameterized Timestep and Clock objects -# - -abstract type AbstractTimestep end - -struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep - t::Int -end - -struct VariableTimestep{TIMES} <: AbstractTimestep - t::Int - current::Int - - function VariableTimestep{TIMES}(t::Int = 1) where {TIMES} - # The special case below handles when functions like next_step step beyond - # the end of the TIMES array. The assumption is that the length of this - # last timestep, starting at TIMES[end], is 1. - current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t] - - return new(t, current) - end -end - -mutable struct Clock{T <: AbstractTimestep} - ts::T - - function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T - return new(FixedTimestep{FIRST, STEP, LAST}(1)) - end - - function Clock{T}(TIMES::NTuple{N, Int} where N) where T - return new(VariableTimestep{TIMES}()) - end -end - -mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} - data::Array{T, N} - - function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} - return new(d) - end - - function TimestepArray{T_TS, T, N}(lengths::Int...) where {T_TS, T, N} - return new(Array{T, N}(undef, lengths...)) - end -end - -# Since these are the most common cases, we define methods (in time.jl) -# specific to these type aliases, avoiding some of the inefficiencies -# associated with an arbitrary number of dimensions. -const TimestepMatrix{T_TS, T} = TimestepArray{T_TS, T, 2} -const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1} - -# -# 2. Dimensions -# - -abstract type AbstractDimension end - -const DimensionKeyTypes = Union{AbstractString, Symbol, Int, Float64} -const DimensionRangeTypes = Union{UnitRange{Int}, StepRange{Int, Int}} - -struct Dimension{T <: DimensionKeyTypes} <: AbstractDimension - dict::OrderedDict{T, Int} - - function Dimension(keys::Vector{T}) where {T <: DimensionKeyTypes} - dict = OrderedDict(collect(zip(keys, 1:length(keys)))) - return new{T}(dict) - end - - function Dimension(rng::T) where {T <: DimensionRangeTypes} - return Dimension(collect(rng)) - end - - Dimension(i::Int) = Dimension(1:i) - - # Support Dimension(:foo, :bar, :baz) - function Dimension(keys::T...) where {T <: DimensionKeyTypes} - vector = [key for key in keys] - return Dimension(vector) - end -end - -# -# Simple optimization for ranges since indices are computable. -# Unclear whether this is really any better than simply using -# a dict for all cases. Might scrap this in the end. -# -mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension - range::T - end - -# -# 3. Types supporting Parameters and their connections -# -abstract type ModelParameter end - -# TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? - -mutable struct ScalarModelParameter{T} <: ModelParameter - value::T - - function ScalarModelParameter{T}(value::T) where T - new(value) - end - - function ScalarModelParameter{T1}(value::T2) where {T1, T2} - try - new(T1(value)) - catch err - error("Failed to convert $value::$T2 to $T1") - end - end -end - -mutable struct ArrayModelParameter{T} <: ModelParameter - values::T - dimensions::Vector{Symbol} # if empty, we don't have the dimensions' name information - - function ArrayModelParameter{T}(values::T, dims::Vector{Symbol}) where T - new(values, dims) - end -end - -ScalarModelParameter(value) = ScalarModelParameter{typeof(value)}(value) - -Base.convert(::Type{ScalarModelParameter{T}}, value::Number) where {T} = ScalarModelParameter{T}(T(value)) - -Base.convert(::Type{T}, s::ScalarModelParameter{T}) where {T} = T(s.value) - -ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(value)}(value, dims) - - -abstract type AbstractConnection end - -struct InternalParameterConnection <: AbstractConnection - src_comp_name::Symbol - src_var_name::Symbol - dst_comp_name::Symbol - dst_par_name::Symbol - ignoreunits::Bool - backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing - offset::Int - - function InternalParameterConnection(src_comp::Symbol, src_var::Symbol, dst_comp::Symbol, dst_par::Symbol, - ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) - self = new(src_comp, src_var, dst_comp, dst_par, ignoreunits, backup, offset) - return self - end -end - -struct ExternalParameterConnection <: AbstractConnection - comp_name::Symbol - param_name::Symbol # name of the parameter in the component - external_param::Symbol # name of the parameter stored in md.ccd.external_params -end - -# -# 4. Types supporting structural definition of models and their components -# - -# To identify components, we create a variable with the name of the component -# whose value is an instance of this type, e.g. -# const global adder = ComponentId(module_name, comp_name) -struct ComponentId - module_name::Symbol - comp_name::Symbol -end - -ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) - -# -# TBD: consider a naming protocol that adds Cls to class struct names -# so it's obvious in the code. -# - -# Objects with a `name` attribute -@class NamedObj begin - name::Symbol -end - -""" - nameof(obj::NamedDef) = obj.name - -Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, -`CompositeComponentDef`, `DatumReference` and `DimensionDef`. -""" -@method Base.nameof(obj::NamedObj) = obj.name - -# Stores references to the name of a component variable or parameter -@class DatumReference <: NamedObj begin - comp_id::ComponentId -end - -# *Def implementation doesn't need to be performance-optimized since these -# are used only to create *Instance objects that are used at run-time. With -# this in mind, we don't create dictionaries of vars, params, or dims in the -# ComponentDef since this would complicate matters if a user decides to -# add/modify/remove a component. Instead of maintaining a secondary dict, -# we just iterate over sub-components at run-time as needed. - -global const BindingTypes = Union{Int, Float64, DatumReference} - -@class DimensionDef <: NamedObj - -# Similar structure is used for variables and parameters (parameters merely adds `default`) -@class mutable DatumDef <: NamedObj begin - datatype::DataType - dimensions::Vector{Symbol} - description::String - unit::String -end - -@class mutable VariableDef <: DatumDef - -@class mutable ParameterDef <: DatumDef begin - # ParameterDef adds a default value, which can be specified in @defcomp - default::Any -end - -@class mutable ComponentDef <: NamedObj begin - comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) - variables::OrderedDict{Symbol, VariableDef} - parameters::OrderedDict{Symbol, ParameterDef} - dimensions::OrderedDict{Symbol, DimensionDef} - first::Union{Nothing, Int} - last::Union{Nothing, Int} - is_uniform::Bool - - # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) - if (is_leaf(self) && comp_id === nothing) - error("Leaf ComponentDef objects must have a Symbol name (not nothing)") - end - - self.comp_id = comp_id - self.name = comp_name - self.variables = OrderedDict{Symbol, VariableDef}() - self.parameters = OrderedDict{Symbol, ParameterDef}() - self.dimensions = OrderedDict{Symbol, DimensionDef}() - self.first = self.last = nothing - self.is_uniform = true - return self - end - - function ComponentDef(comp_id::Union{Nothing, ComponentId}, comp_name::Symbol=comp_id.comp_name) - return ComponentDef(new(), comp_id, comp_name) - end - - # ComponentDef() = ComponentDef(nothing, gensym("anonymous")) -end - -@class mutable CompositeComponentDef <: ComponentDef begin - comps_dict::OrderedDict{Symbol, _ComponentDef_} - bindings::Vector{Pair{DatumReference, BindingTypes}} - exports::Vector{Pair{DatumReference, Symbol}} - - internal_param_conns::Vector{InternalParameterConnection} - external_param_conns::Vector{ExternalParameterConnection} - external_params::Dict{Symbol, ModelParameter} - - # Names of external params that the ConnectorComps will use as their :input2 parameters. - backups::Vector{Symbol} - - sorted_comps::Union{Nothing, Vector{Symbol}} - - function CompositeComponentDef(comps::Vector{T}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} - self = new() - - # superclass initialization - # ComponentDef(self, comp_id, comp_name) - - self.comps_dict = OrderedDict{Symbol, T}([nameof(cd) => cd for cd in comps]) - self.bindings = bindings - self.exports = exports - self.internal_param_conns = Vector{InternalParameterConnection}() - self.external_param_conns = Vector{ExternalParameterConnection}() - self.external_params = Dict{Symbol, ModelParameter}() - self.backups = Vector{Symbol}() - self.sorted_comps = nothing - - return self - end - - function CompositeComponentDef() - comps = Vector{<: _ComponentDef_}() - bindings = Vector{Pair{DatumReference, BindingTypes}}() - exports = Vector{Pair{DatumReference, Symbol}}() - return CompositeComponentDef(comps, bindings, exports) - end -end - -# Move to defs.jl? - -@method is_leaf(comp::ComponentDef) = true -@method is_leaf(comp::CompositeComponentDef) = false -@method is_composite(comp::ComponentDef) = !is_leaf(comp) - -# TBD: Does a ModelDef contain a CompositeComponentDef, or is it a subclass? - -@class mutable ModelDef <: CompositeComponentDef begin - dimensions2::Dict{Symbol, Dimension} # TBD: use the one in ccd instead - number_type::DataType - - # TBD: these methods assume sub-elements rather than subclasses - function ModelDef(ccd::CompositeComponentDef, number_type::DataType=Float64) - dimensions = Dict{Symbol, Dimension}() - return new(ccd, dimensions, number_type) - end - - function ModelDef(number_type::DataType=Float64) - # passes an anonymous top-level (composite) ComponentDef - return ModelDef(ComponentDef(), number_type) - end -end - -# -# 5. Types supporting instantiated models and their components -# - -# Supertype for variables and parameters in component instances -@class ComponentInstanceData{NT <: NamedTuple} begin - nt::NT -end - -@class ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData - -@class ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData - -function ComponentInstanceParameters(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceParameters{NT}(NT(values)) -end - -function ComponentInstanceParameters{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceParameters{NT}(NT(values)) -end - -function ComponentInstanceVariables(names, types, values) - NT = NamedTuple{names, types} - ComponentInstanceVariables{NT}(NT(values)) -end - -function ComponentInstanceVariables{NT}(values::T) where {NT <: NamedTuple, T <: AbstractArray} - ComponentInstanceVariables{NT}(NT(values)) -end - -# Move to instances.jl? - -@method nt(obj::ComponentInstanceData) = getfield(obj, :nt) -@method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) -@method Base.values(obj::ComponentInstanceData) = values(nt(obj)) -@method types(obj::ComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters - -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin - comp_name::Symbol - comp_id::ComponentId - variables::TV - parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} - first::Union{Nothing, Int} - last::Union{Nothing, Int} - init::Union{Nothing, Function} - run_timestep::Union{Nothing, Function} - - function ComponentInstance{TV, TP}(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = new{TV, TP}() - self.comp_id = comp_id = comp_def.comp_id - self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # set in "build" stage - self.variables = vars - self.parameters = pars - self.first = comp_def.first - self.last = comp_def.last - - comp_module = Base.eval(Main, comp_id.module_name) - - # The try/catch allows components with no run_timestep function (as in some of our test cases) - # All ComponentInstances use a standard method that just loops over inner components. - # TBD: use FunctionWrapper here? - function get_func(name) - func_name = Symbol("$(name)_$(self.comp_name)") - try - Base.eval(comp_module, func_name) - catch err - nothing - end - end - - # `is_composite` indicates a ComponentInstance used to store summary - # data for ComponentInstance and is not itself runnable. - self.run_timestep = is_composite(self) ? nothing : get_func("run_timestep") - self.init = is_composite(self) ? nothing : get_func("init") - - return self - end -end - -function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - ComponentInstance{TV, TP}(comp_def, vars, pars, name, subcomps=subcomps) -end - -@class mutable CompositeInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: ComponentInstance begin - comps_dict::OrderedDict{Symbol, _ComponentInstance_} - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} - clocks::Vector{Clock} - - function CompositeInstance{TV, TP}(comps::Vector{T}) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters, T <: _ComponentInstance_} - self = new() - self.comps_dict = OrderedDict{Symbol, T}([ci.comp_name => ci for ci in comps]) - self.firsts = Vector{Int}() - self.lasts = Vector{Int}() - self.clocks = Vector{Clock}() - return self - end -end - -@method is_leaf(ci::ComponentInstance) = true -@method is_leaf(ci::CompositeInstance) = false -@method is_composite(ci::ComponentInstance) = !is_leaf(ci) - -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimDict - dict::Dict{Symbol, Vector{Int}} -end - -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimDict of dimensions by name -# as the "d" parameter. -@inline function Base.getproperty(dimdict::DimDict, property::Symbol) - return getfield(dimdict, :dict)[property] -end - -nt(obj::T) where {T <: ComponentInstanceData} = getfield(obj, :nt) -Base.names(obj::T) where {T <: ComponentInstanceData} = keys(nt(obj)) -Base.values(obj::T) where {T <: ComponentInstanceData} = values(nt(obj)) -types(obj::T) where {T <: ComponentInstanceData} = typeof(nt(obj)).parameters[2].parameters - -# TBD: should this be @class or struct? -# ModelInstance holds the built model that is ready to be run -mutable struct ModelInstance - md::ModelDef - cci::Union{Nothing, CompositeInstance} - - function ModelInstance(md::ModelDef, cci::Union{Nothing, CompositeInstance}=nothing) - return new(md, cci) - end -end - -# -# 6. User-facing Model types providing a simplified API to model definitions and instances. -# -""" - Model - -A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). -This `Model` can be created with the optional keyword argument `number_type` indicating -the default type of number used for the `ModelDef`. If not specified the `Model` assumes -a `number_type` of `Float64`. -""" -mutable struct Model - md::ModelDef - mi::Union{Nothing, ModelInstance} - - function Model(number_type::DataType=Float64) - return new(ModelDef(number_type), nothing) - end - - # Create a copy of a model, e.g., to create marginal models - function Model(m::Model) - return new(deepcopy(m.md), nothing) - end -end - -""" - MarginalModel - -A Mimi `Model` whose results are obtained by subtracting results of one `base` Model -from those of another `marginal` Model` that has a difference of `delta`. -""" -struct MarginalModel - base::Model - marginal::Model - delta::Float64 - - function MarginalModel(base::Model, delta::Float64=1.0) - return new(base, Model(base), delta) - end -end - -function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) - return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta -end - -# -# 7. Reference types provide more convenient syntax for interrogating Components -# - -""" - ComponentReference - -A container for a component, for interacting with it within a model. -""" -struct ComponentReference - model::Model - comp_name::Symbol -end - -""" - VariableReference - -A container for a variable within a component, to improve connect_param! aesthetics, -by supporting subscripting notation via getindex & setindex . -""" -struct VariableReference - model::Model - comp_name::Symbol - var_name::Symbol -end diff --git a/test/test_composite.jl b/test/test_composite.jl index 46e89c272..cdc4d9974 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -5,7 +5,7 @@ using Mimi import Mimi: ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, - SubcompsDef, SubcompsDefTypes, build, time_labels, reset_compdefs, compdef + build, time_labels, reset_compdefs, compdef reset_compdefs() @@ -48,7 +48,7 @@ let calling_module = @__MODULE__ ccname = :testcomp ccid = ComponentId(calling_module, ccname) - comps::Vector{ComponentDef{<: SubcompsDefTypes}} = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] + comps::Vector{<: _ComponentDef_} = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] # TBD: need to implement this to create connections and default value bindings::Vector{Pair{DatumReference, BindingTypes}} = [ @@ -63,8 +63,7 @@ let calling_module = @__MODULE__ DatumReference(Comp3, :var_3_1) => :c3v1 ] - subcomps = SubcompsDef(comps, bindings, exports) - MyComposite.md = ModelDef(ComponentDef(ccid, ccname, subcomps)) + MyComposite.md = ModelDef(ComponentDef(ccid, ccname, comps, bindings, exports)) set_dimension!(MyComposite, :time, 2005:2020) nothing From 0a708263bc5f89b4fe203ec10d135b2aa67e7935 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 6 Dec 2018 16:42:23 -0800 Subject: [PATCH 22/81] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bb855d827..b1878702c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ docs/site/ .DS_Store benchmark/tune.json docs/Manifest.toml +types-old.jl From d29cfa77c80e5ccb3cdf600eec98e5d39499884e Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 14 Dec 2018 13:29:26 -0800 Subject: [PATCH 23/81] WIP - Checkpoint component comp work, other minor cleanup --- src/components/adder.jl | 2 - src/core/build.jl | 165 ++++++++++++------------- src/core/connections.jl | 60 +++------ src/core/defs.jl | 254 +++++++++++++++++--------------------- src/core/instances.jl | 152 +++++++---------------- src/core/model.jl | 28 +++-- src/core/types.jl | 65 ++++++---- src/mcs/montecarlo.jl | 10 +- src/utils/getdataframe.jl | 14 ++- src/utils/graph.jl | 6 +- test/mcs/test_defmcs.jl | 3 +- test/test_components.jl | 8 +- test/test_composite.jl | 4 +- 13 files changed, 337 insertions(+), 434 deletions(-) diff --git a/src/components/adder.jl b/src/components/adder.jl index 0a9bc6298..2fd191989 100644 --- a/src/components/adder.jl +++ b/src/components/adder.jl @@ -1,7 +1,5 @@ using Mimi -# When evaluated in the __init__() function, the surrounding module -# is Main rather than Mimi. @defcomp adder begin add = Parameter(index=[time]) input = Parameter(index=[time]) diff --git a/src/core/build.jl b/src/core/build.jl index 753c638ab..27cd028d1 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -1,8 +1,8 @@ connector_comp_name(i::Int) = Symbol("ConnectorComp$i") # Return the datatype to use for instance variables/parameters -function _instance_datatype(md::ModelDef, def::DatumDef) # TBD: supertype(DatumDef) or _DatumDef_ - dtype = def.datatype == Number ? number_type(md) : def.datatype +function _instance_datatype(md::ModelDef, def::absclass(DatumDef)) + dtype = def.datatype == Number ? number_type(md) : datatype(def) dims = dimensions(def) num_dims = dim_count(def) @@ -27,7 +27,7 @@ function _instance_datatype(md::ModelDef, def::DatumDef) # TB end # Create the Ref or Array that will hold the value(s) for a Parameter or Variable -function _instantiate_datum(md::ModelDef, def::DatumDef) # TBD: supertype(DatumDef) or _DatumDef_ +function _instantiate_datum(md::ModelDef, def::absclass(DatumDef)) dtype = _instance_datatype(md, def) dims = dimensions(def) num_dims = length(dims) @@ -81,7 +81,7 @@ function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{ for (dr, name) in comp_def.exports if is_variable(dr) obj = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? - value = getproperty(obj, dr.datum_name) + value = getproperty(obj, nameof(dr)) push!(names, name) push!(values, value) end @@ -98,7 +98,7 @@ function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{ for (dr, name) in comp_def.exports if is_parameter(dr) d = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? - value = d[dr.datum_name] + value = d[nameof(dr)] push!(names, name) push!(values, value) end @@ -108,70 +108,76 @@ function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{ return ComponentInstanceParameters(Tuple(names), Tuple{types...}, Tuple(values)) end +function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + comp_name = nameof(comp_def) + par_dict[comp_name] = Dict() + + var_dict[comp_name] = v = _instantiate_component_vars(md, comp_def) + @info "_instantiate_vars leaf $comp_name: $v" +end + +# Creates the top-level vars for the model +function _instantiate_vars(md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + _instantiate_vars(md, md, var_dict, par_dict) +end -# TBD: define two methods, one on comp_def::ComponentDef and other on comp_def::CompositeComponentDef # Recursively instantiate all variables and store refs in the given dict. -function _instantiate_vars(md::ModelDef, comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +@method function _instantiate_vars(comp_def::CompositeComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) comp_name = nameof(comp_def) par_dict[comp_name] = Dict() - if is_composite(comp_def) - @info "_instantiate_vars composite $comp_name" - for cd in compdefs(comp_def) - _instantiate_vars(md, cd, var_dict, par_dict) - end - var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) - @info "composite vars for $comp_name: $v " - else - var_dict[comp_name] = v = _instantiate_component_vars(md, comp_def) - @info "_instantiate_vars leaf $comp_name: $v" + @info "_instantiate_vars composite $comp_name" + for cd in compdefs(comp_def) + _instantiate_vars(md, cd, var_dict, par_dict) end + var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) + @info "composite vars for $comp_name: $v " end -# TBD: Define only for CompositeComponentDef? -# Recursively collect all parameters with connections to allocated storage for variablesa -function _collect_params(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - if is_composite(comp_def) - # depth-first search - for cd in compdefs(comp_def) - _collect_params(cd, var_dict, par_dict) - end - - @info "Collecting params for $(comp_def.comp_id)" - - # Iterate over connections to create parameters, referencing storage in vars - for ipc in internal_param_conns(comp_def) - src_vars = var_dict[ipc.src_comp_name] - var_value_obj = get_property_obj(src_vars, ipc.src_var_name) - comp_pars = par_dict[ipc.dst_comp_name] - comp_pars[ipc.dst_par_name] = var_value_obj - @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" - end +# Do nothing if called on a leaf component +_collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing + +# Recursively collect all parameters with connections to allocated storage for variables +@method function _collect_params(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + # depth-first search of composites + for cd in compdefs(comp_def) + _collect_params(cd, var_dict, par_dict) + end + + @info "Collecting params for $(comp_def.comp_id)" + + # Iterate over connections to create parameters, referencing storage in vars + for ipc in internal_param_conns(comp_def) + src_vars = var_dict[ipc.src_comp_name] + var_value_obj = get_property_obj(src_vars, ipc.src_var_name) + comp_pars = par_dict[ipc.dst_comp_name] + comp_pars[ipc.dst_par_name] = var_value_obj + @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" + end - for ext in external_param_conns(comp_def) - param = external_param(comp_def, ext.external_param) - comp_pars = par_dict[ext.comp_name] - comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) - @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" - end + for ext in external_param_conns(comp_def) + param = external_param(comp_def, ext.external_param) + comp_pars = par_dict[ext.comp_name] + comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) + @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" + end - # Make the external parameter connections for the hidden ConnectorComps. - # Connect each :input2 to its associated backup value. - for (i, backup) in enumerate(backups(comp_def)) - conn_comp_name = connector_comp_name(i) - param = external_param(comp_def, backup) - comp_pars = par_dict[conn_comp_name] - comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) - @info "backup: $conn_comp_name $param" - end + # Make the external parameter connections for the hidden ConnectorComps. + # Connect each :input2 to its associated backup value. + for (i, backup) in enumerate(backups(comp_def)) + conn_comp_name = connector_comp_name(i) + param = external_param(comp_def, backup) + comp_pars = par_dict[conn_comp_name] + comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) + @info "backup: $conn_comp_name $param" end end # Save a reference to the model's dimension dictionary to make it # available in calls to run_timestep. function _save_dim_dict_reference(mi::ModelInstance) - dim_dict = dim_value_dict(mi.md) + dim_dict = dim_value_dict(mi) for ci in components(mi) ci.dim_dict = dim_dict @@ -180,53 +186,46 @@ function _save_dim_dict_reference(mi::ModelInstance) return nothing end -# TBD: -# _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) = _combine_exported_pars(comp_def, par_dict) - function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @info "Instantiating params for $(comp_def.comp_id)" - # TBD: drop if, use only else branch - if is_composite(comp_def) - return _combine_exported_pars(comp_def, par_dict) + comp_name = nameof(comp_def) + d = par_dict[comp_name] - else - comp_name = nameof(comp_def) - d = par_dict[comp_name] - - pnames = Tuple(parameter_names(comp_def)) - pvals = [d[pname] for pname in pnames] - ptypes = Tuple{map(typeof, pvals)...} - return ComponentInstanceParameters(pnames, ptypes, pvals) - end + pnames = Tuple(parameter_names(comp_def)) + pvals = [d[pname] for pname in pnames] + ptypes = Tuple{map(typeof, pvals)...} + + return ComponentInstanceParameters(pnames, ptypes, pvals) end +@method _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) = _combine_exported_pars(comp_def, par_dict) + + # Return a built leaf or composite ComponentInstance function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - @info "_build $(comp_def.comp_id)" + @info "_build leaf $(comp_def.comp_id)" @info " var_dict $(var_dict)" @info " par_dict $(par_dict)" - comp_name = nameof(comp_def) + comp_name = nameof(comp_def) pars = _instantiate_params(comp_def, par_dict) vars = var_dict[comp_name] + return ComponentInstance(comp_def, vars, pars, comp_name) end -function _build(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +@method function _build(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @info "_build composite $(comp_def.comp_id)" @info " var_dict $(var_dict)" @info " par_dict $(par_dict)" - comp_name = nameof(comp_def) - - built_comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def)] - - pars = _instantiate_params(comp_def, par_dict) - vars = var_dict[comp_name] - return CompositeInstance(comp_def, vars, pars, comp_name) + + comp_name = nameof(comp_def) + comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def)] + + return CompositeComponentInstance(comps, comp_def, comp_name) end - function _build(md::ModelDef) add_connector_comps(md) @@ -238,18 +237,16 @@ function _build(md::ModelDef) error(msg) end - var_dict = Dict{Symbol, Any}() # collect all var defs and par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go - comp_def = md.ccd - _instantiate_vars(md, comp_def, var_dict, par_dict) - _collect_params(comp_def, var_dict, par_dict) + _instantiate_vars(md, var_dict, par_dict) + _collect_params(md, var_dict, par_dict) @info "var_dict: $var_dict" @info "par_dict: $par_dict" - ci = _build(comp_def, var_dict, par_dict) + ci = _build(md, var_dict, par_dict) mi = ModelInstance(md, ci) _save_dim_dict_reference(mi) return mi @@ -271,7 +268,7 @@ which shares the internal `ModelDef` between the `base` and `marginal`. function create_marginal_model(base::Model, delta::Float64=1.0) # Make sure the base has a ModelInstance before we copy since this # copies the ModelDef to avoid being affected by later changes. - if base.mi === nothing + if ! is_built(base) build(base) end diff --git a/src/core/connections.jl b/src/core/connections.jl index f358e37f2..d887949a0 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -14,10 +14,7 @@ function disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol) end # Default string, string unit check function -function verify_units(one::AbstractString, two::AbstractString) - # True if and only if they match - return one == two -end +verify_units(unit1::AbstractString, unit2::AbstractString) = (unit1 == unit2) function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) param_def = parameter(comp_def, param_name) @@ -62,12 +59,6 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, end end -@delegate backups(md::ModelDef) => ccd -backups(obj::CompositeComponentDef) = obj.backups - -@delegate add_backup!(md::ModelDef, obj) => ccd -add_backup!(ccd::CompositeComponentDef, obj) = push!(ccd.backups, obj) - """ connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) @@ -143,7 +134,7 @@ function connect_param!(md::ModelDef, if isuniform(md) # use the first from the comp_def not the ModelDef - _, stepsize = first_and_step(md) + stepsize = step_size(md) values = TimestepArray{FixedTimestep{first, stepsize}, T, dim_count}(backup) else times = time_labels(md) @@ -159,11 +150,13 @@ function connect_param!(md::ModelDef, else # If backup not provided, make sure the source component covers the span of the destination component - src_first, src_last = first_period(md, src_comp_def), last_period(md, src_comp_def) - dst_first, dst_last = first_period(md, dst_comp_def), last_period(md, dst_comp_def) + src_first, src_last = first_and_last(md, src_comp_def) + dst_first, dst_last = first_and_last(md, dst_comp_def) if dst_first < src_first || dst_last > src_last - error("Cannot connect parameter; $src_comp_name only runs from $src_first to $src_last, whereas $dst_comp_name runs from $dst_first to $dst_last. Backup data must be provided for missing years. Try calling: - `connect_param!(m, comp_name, par_name, comp_name, var_name, backup_data)`") + error("""Cannot connect parameter: $src_comp_name runs only from $src_first to $src_last, +whereas $dst_comp_name runs from $dst_first to $dst_last. Backup data must be provided for missing years. +Try calling: + `connect_param!(m, comp_name, par_name, comp_name, var_name, backup_data)`""") end backup_param_name = nothing @@ -255,26 +248,19 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T nothing end -@delegate internal_param_conns(md::ModelDef) => ccd - # Find internal param conns to a given destination component -function internal_param_conns(md::ModelDef, dst_comp_name::Symbol) - return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(md)) +@method function internal_param_conns(obj::CompositeComponentDef, dst_comp_name::Symbol) + return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(obj)) end -external_param_conns(ccd::CompositeComponentDef) = ccd.external_param_conns -@delegate external_param_conns(md::ModelDef) => ccd - # Find external param conns for a given comp -function external_param_conns(md::ModelDef, comp_name::Symbol) - return filter(x -> x.comp_name == comp_name, external_param_conns(md)) +@method function external_param_conns(obj::CompositeComponentDef, comp_name::Symbol) + return filter(x -> x.comp_name == comp_name, external_param_conns(obj)) end -@delegate external_param(md::ModelDef, name::Symbol) => ccd - -function external_param(ccd::CompositeComponentDef, name::Symbol) +function external_param(obj::CompositeComponentDef, name::Symbol) try - return ccd.external_params[name] + return obj.external_params[name] catch err if err isa KeyError error("$name not found in external parameter list") @@ -284,22 +270,16 @@ function external_param(ccd::CompositeComponentDef, name::Symbol) end end -@delegate add_internal_param_conn!(md::ModelDef, conn::InternalParameterConnection) => ccd - -function add_internal_param_conn!(ccd::CompositeComponentDef, conn::InternalParameterConnection) - push!(ccd.internal_param_conns, conn) +@method function add_internal_param_conn!(obj::CompositeComponentDef, conn::InternalParameterConnection) + push!(obj.internal_param_conns, conn) end -@delegate add_external_param_conn!(md::ModelDef, conn::ExternalParameterConnection) => ccd - -function add_external_param_conn!(ccd::CompositeComponentDef, conn::ExternalParameterConnection) - push!(ccd.external_param_conns, conn) +@method function add_external_param_conn!(obj::CompositeComponentDef, conn::ExternalParameterConnection) + push!(obj.external_param_conns, conn) end -@delegate set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter) => ccd - -function set_external_param!(ccd::CompositeComponentDef, name::Symbol, value::ModelParameter) - ccd.external_params[name] = value +@method function set_external_param!(obj::CompositeComponentDef, name::Symbol, value::ModelParameter) + obj.external_params[name] = value end function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) diff --git a/src/core/defs.jl b/src/core/defs.jl index 81146c4ed..1b36ad3cd 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -24,19 +24,12 @@ end @method hascomp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) @method compdef(c::CompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] -# TBD: delegation not needed if ModelDef is a subclass of CompositeComponentDef -@delegate compdefs(md::ModelDef) => ccd -@delegate compkeys(md::ModelDef) => ccd -@delegate hascomp(md::ModelDef, comp_name::Symbol) => ccd -@delegate compdef(md::ModelDef, comp_name::Symbol) => ccd - # Return the module object for the component was defined in compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name -# TBD: needed for composites? -@delegate compname(obj::ComponentDef) => comp_id -@delegate compmodule(obj::ComponentDef) => comp_id +@method compmodule(obj::ComponentDef) = compmodule(obj.comp_id) +@method compname(obj::ComponentDef) = compname(obj.comp_id) function reset_compdefs(reload_builtins=true) empty!(_compdefs) @@ -92,10 +85,8 @@ end number_type(md::ModelDef) = md.number_type -@delegate numcomponents(md::ModelDef) => ccd - numcomponents(obj::ComponentDef) = 0 # no sub-components -numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) +@method numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) function dump_components() for comp in compdefs() @@ -112,8 +103,8 @@ end """ new_comp(comp_id::ComponentId, verbose::Bool=true) -Add an empty `LeafComponentDef` to the global component registry with the given -`comp_id`. The empty `LeafComponentDef` must be populated with calls to `addvariable`, +Add an empty `ComponentDef` to the global component registry with the given +`comp_id`. The empty `ComponentDef` must be populated with calls to `addvariable`, `addparameter`, etc. Use `@defcomposite` to create composite components. """ function new_comp(comp_id::ComponentId, verbose::Bool=true) @@ -125,7 +116,7 @@ function new_comp(comp_id::ComponentId, verbose::Bool=true) end end - comp_def = LeafComponentDef(comp_id) + comp_def = ComponentDef(comp_id) _compdefs[comp_id] = comp_def return comp_def end @@ -155,16 +146,14 @@ end # Dimensions # -function add_dimension!(comp::LeafComponentDef, name) +@method function add_dimension!(comp::ComponentDef, name) comp.dimensions[name] = dim_def = DimensionDef(name) return dim_def end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -dimensions(cd::LeafComponentDef) = values(cd.dimensions) - -function dimensions(ccd::CompositeComponentDef) +@method function dimensions(ccd::CompositeComponentDef) dims = Vector{DimensionDef}() for cd in compdefs(ccd) append!(dims, dimensions(cd)) @@ -175,19 +164,18 @@ function dimensions(ccd::CompositeComponentDef) return collect(Set(dims)) end -# TBD: @method -dimensions(def::DatumDef) = def.dimensions - -# TBD: handle CompositeComponentDef -dimensions(comp_def::LeafComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) - -dim_count(def::DatumDef) = length(dimensions(def)) +@method dimensions(comp_def::ComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) -datatype(def::DatumDef) = def.datatype +@method dim_count(def::DatumDef) = length(dimensions(def)) -description(def::DatumDef) = def.description +function step_size(values::Vector{Int}) + return length(values) > 1 ? values[2] - values[1] : 1 +end -unit(def::DatumDef) = def.unit +function step_size(md::ModelDef) + keys::Vector{Int} = time_labels(md) + return step_size(keys) +end function first_and_step(md::ModelDef) keys::Vector{Int} = time_labels(md) # labels are the first times of the model runs @@ -195,7 +183,7 @@ function first_and_step(md::ModelDef) end function first_and_step(values::Vector{Int}) - return values[1], (length(values) > 1 ? values[2] - values[1] : 1) + return values[1], step_size(values) end function time_labels(md::ModelDef) @@ -222,7 +210,7 @@ function check_parameter_dimensions(md::ModelDef, value::AbstractArray, dims::Ve end # TBD: is this needed for composites? -function datum_size(md::ModelDef, comp_def::LeafComponentDef, datum_name::Symbol) +function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) dims = dimensions(comp_def, datum_name) if dims[1] == :time time_length = getspan(md, comp_def)[1] @@ -234,20 +222,16 @@ function datum_size(md::ModelDef, comp_def::LeafComponentDef, datum_name::Symbol return datum_size end -has_dim(ccd::CompositeComponentDef, name::Symbol) = haskey(ccd.dimensions, name) -@delegate has_dim(md::ModelDef, name::Symbol) => ccd +@method has_dim(obj::CompositeComponentDef, name::Symbol) = haskey(obj.dimensions, name) -isuniform(ccd::CompositeComponentDef) = ccd.is_uniform -@delegate isuniform(md::ModelDef) => ccd +@method isuniform(obj::CompositeComponentDef) = obj.is_uniform -set_uniform!(ccd::CompositeComponentDef, value::Bool) = (ccd.is_uniform = value) -@delegate set_uniform!(md::ModelDef, value::Bool) => ccd +@method set_uniform!(obj::CompositeComponentDef, value::Bool) = (obj.is_uniform = value) + +@method dimension(obj::CompositeComponentDef, name::Symbol) = obj.dimensions[name] dimensions(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] -@delegate dimensions(md::ModelDef) => ccd -dimension(ccd::CompositeComponentDef, name::Symbol) = ccd.dimensions[name] -@delegate dimension(md::ModelDef, name::Symbol) => ccd dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dimensions(md)]) dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dimensions(md, dims)] @@ -295,9 +279,7 @@ end Set the values of `md` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. """ -@delegate set_dimension!(md::ModelDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => ccd - -function set_dimension!(cmd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) +@method function set_dimension!(cmd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) redefined = has_dim(ccd, name) if redefined @warn "Redefining dimension :$name" @@ -313,8 +295,8 @@ function set_dimension!(cmd::CompositeComponentDef, name::Symbol, keys::Union{In return set_dimension!(md.ccd, name, Dimension(keys)) end -function set_dimension!(ccd::CompositeComponentDef, name::Symbol, dim::Dimension) - ccd.dimensions[name] = dim +@method function set_dimension!(obj::CompositeComponentDef, name::Symbol, dim::Dimension) + obj.dimensions[name] = dim end # helper functions used to determine if the provided time values are @@ -341,12 +323,7 @@ end # Parameters # -@method external_params(obj::CompositeComponentDef) = obj.external_params - -# TBD: Delete if ModelDef subclasses CompositeComponentDef -@delegate external_params(md::ModelDef) => ccd - -function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) +@method function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, datatype, dimensions, description, unit, :parameter, default) comp_def.parameters[name] = p return p @@ -363,7 +340,7 @@ Return a list of the parameter definitions for `comp_def`. """ parameters(obj::ComponentDef) = values(obj.parameters) -function parameters(ccd::CompositeComponentDef) +@method function parameters(ccd::CompositeComponentDef) pars = ccd.parameters # return cached parameters, if any @@ -379,8 +356,6 @@ function parameters(ccd::CompositeComponentDef) return values(pars) end -@delegate parameters(md::ModelDef) => ccd - """ parameters(comp_id::ComponentId) @@ -388,7 +363,7 @@ Return a list of the parameter definitions for `comp_id`. """ parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) -parameters(dr::DatumReference) = parameters(dr.comp_id) +@method parameters(obj::DatumReference) = parameters(obj.comp_id) """ parameter_names(md::ModelDef, comp_name::Symbol) @@ -400,33 +375,27 @@ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, c #parameter_names(comp_def::ComponentDef) = [nameof(param) for param in parameters(comp_def)] parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) -parameter(md::ModelDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(md, comp_name), param_name) - -@delegate parameter(md::ModelDef, param_name::Symbol) => ccd +@method parameter(obj::CompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) -parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), nameof(dr)) - -function parameter(cd::ComponentDef, name::Symbol) - if is_composite(cd) - parameters(cd) # make sure values have been gathered - end +@method parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), nameof(dr)) +@method function parameter(obj::ComponentDef, name::Symbol) try - return cd.parameters[name] + return obj.parameters[name] catch - error("Parameter $name was not found in component $(nameof(cd))") + error("Parameter $name was not found in component $(nameof(obj))") end end -has_parameter(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.parameters, name) +@method has_parameter(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.parameters, name) -function parameter_unit(md::ModelDef, comp_name::Symbol, param_name::Symbol) - param = parameter(md, comp_name, param_name) +@method function parameter_unit(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) + param = parameter(obj, comp_name, param_name) return param.unit end -function parameter_dimensions(md::ModelDef, comp_name::Symbol, param_name::Symbol) - param = parameter(md, comp_name, param_name) +function parameter_dimensions(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) + param = parameter(obj, comp_name, param_name) return param.dimensions end @@ -473,7 +442,7 @@ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, first = first_period(md, comp_def) if isuniform(md) - _, stepsize = first_and_step(md) + stepsize = step_size(md) values = TimestepArray{FixedTimestep{first, stepsize}, T, num_dims}(value) else times = time_labels(md) @@ -500,7 +469,7 @@ end # # Variables # -variables(comp_def::LeafComponentDef) = values(comp_def.variables) +variables(comp_def::ComponentDef) = values(comp_def.variables) function variables(ccd::CompositeComponentDef) vars = ccd.variables @@ -509,8 +478,8 @@ function variables(ccd::CompositeComponentDef) if length(vars) == 0 for (dr, name) in ccd.exports cd = compdef(dr.comp_id) - if has_variable(cd, dr.datum_name) - vars[name] = variable(cd, dr.datum_name) + if has_variable(cd, nameof(dr)) + vars[name] = variable(cd, nameof(dr)) end end end @@ -540,7 +509,7 @@ variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), va variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(md, comp_name), var_name) -variable(dr::DatumReference) = variable(compdef(dr.comp_id), dr.datum_name) +variable(dr::DatumReference) = variable(compdef(dr.comp_id), nameof(dr)) has_variable(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.variables, name) @@ -564,23 +533,23 @@ function variable_dimensions(md::ModelDef, comp_name::Symbol, var_name::Symbol) return var.dimensions end -# Add a variable to a LeafComponentDef. CompositeComponents have no vars of their own, +# Add a variable to a ComponentDef. CompositeComponents have no vars of their own, # only references to vars in components contained within. -function addvariable(comp_def::LeafComponentDef, name, datatype, dimensions, description, unit) +function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) var_def = DatumDef(name, datatype, dimensions, description, unit, :variable) comp_def.variables[name] = var_def return var_def end """ - addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) + addvariables(obj::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) Add all exported variables to a CompositeComponentDef. """ -function addvariables(comp_def::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) +@method function addvariables(obj::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) # TBD: this needs attention for (dr, exp_name) in exports - addvariable(comp_def, variable(comp_def, nameof(variable)), exp_name) + addvariable(obj, variable(obj, nameof(variable)), exp_name) end end @@ -594,23 +563,23 @@ end # # Return the number of timesteps a given component in a model will run for. -function getspan(md::ModelDef, comp_name::Symbol) - comp_def = compdef(md, comp_name) - return getspan(md, comp_def) +@method function getspan(obj::CompositeComponentDef, comp_name::Symbol) + comp_def = compdef(obj, comp_name) + return getspan(obj, comp_def) end -function getspan(md::ModelDef, comp_def::ComponentDef) - first = first_period(md, comp_def) - last = last_period(md, comp_def) - times = time_labels(md) +@method function getspan(obj::CompositeComponentDef, comp_def::ComponentDef) + first = first_period(obj, comp_def) + last = last_period(obj, comp_def) + times = time_labels(obj) first_index = findfirst(isequal(first), times) last_index = findfirst(isequal(last), times) return size(times[first_index:last_index]) end -function set_run_period!(comp_def::LeafComponentDef, first, last) - comp_def.first = first - comp_def.last = last +@method function set_run_period!(comp_def::ComponentDef, first, last) + first!(comp_def, first) + last!(comp_def, last) return nothing end @@ -620,30 +589,28 @@ end const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} -function _append_comp!(ccd::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) - ccd.comps_dict[comp_name] = comp_def +@method function _append_comp!(obj::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) + obj.comps_dict[comp_name] = comp_def end -@delegate _append_comp!(md::ModelDef, comp_name::Symbol, comp_def::ComponentDef) => ccd - """ add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_def` to the model indcated by `md`. The component is added at the -end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` -differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. +Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component +is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the +`comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) +@method function add_comp!(obj::CompositeComponentDef, comp_def::ComponentDef, comp_name::Symbol; + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing) # check that a time dimension has been set - if ! has_dim(md, :time) + if ! has_dim(obj, :time) error("Cannot add component to model without first setting time dimension.") end # check that first and last are within the model's time index range - time_index = dim_keys(md, :time) + time_index = dim_keys(obj, :time) if first !== nothing && first < time_index[1] error("Cannot add component $name with first time before first of model's time index range.") @@ -658,7 +625,7 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; end # Check if component being added already exists - if hascomp(md, comp_name) + if hascomp(obj, comp_name) error("Cannot add two components of the same name ($comp_name)") end @@ -671,43 +638,43 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; set_run_period!(comp_def, first, last) if before === nothing && after === nothing - _append_comp!(md, comp_name, comp_def) # just add it to the end + _append_comp!(obj, comp_name, comp_def) # just add it to the end else new_comps = OrderedDict{Symbol, ComponentDef}() if before !== nothing - if ! hascomp(md, before) + if ! hascomp(obj, before) error("Component to add before ($before) does not exist") end - for k in compkeys(md) + for k in compkeys(obj) if k == before new_comps[comp_name] = comp_def end - new_comps[k] = compdef(md, k) + new_comps[k] = compdef(obj, k) end else # after !== nothing, since we've handled all other possibilities above - if ! hascomp(md, after) + if ! hascomp(obj, after) error("Component to add before ($before) does not exist") end - for k in compkeys(md) - new_comps[k] = compdef(md, k) + for k in compkeys(obj) + new_comps[k] = compdef(obj, k) if k == after new_comps[comp_name] = comp_def end end end - md.ccd.comps_dict = new_comps - # println("md.ccd.comp_defs: $(md.ccd.comp_defs)") + comps_dict!(obj, new_comps) + # println("obj.comp_defs: $(comp_defs(obj))") end # Set parameters to any specified defaults for param in parameters(comp_def) if param.default !== nothing - set_param!(md, comp_name, nameof(param), param.default) + set_param!(obj, comp_name, nameof(param), param.default) end end @@ -715,46 +682,45 @@ function add_comp!(md::ModelDef, comp_def::ComponentDef, comp_name::Symbol; end """ - add_comp!(md::ModelDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, + add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_id` to the model indicated by `md`. The component is added at the end of -the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` -differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. +Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component +is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the +`comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -function add_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) +@method function add_comp!(obj::CompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") - add_comp!(md, compdef(comp_id), comp_name, first=first, last=last, before=before, after=after) + add_comp!(obj, compdef(comp_id), comp_name, first=first, last=last, before=before, after=after) end """ - replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; + replace_comp!(obj::CompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) -Replace the component with name `comp_name` in model definition `md` with the -component `comp_id` using the same name. The component is added in the same -position as the old component, unless one of the keywords `before` or `after` -is specified. The component is added with the same first and last values, -unless the keywords `first` or `last` are specified. Optional boolean argument -`reconnect` with default value `true` indicates whether the existing parameter -connections should be maintained in the new component. +Replace the component with name `comp_name` in composite component definition `obj` with the +component `comp_id` using the same name. The component is added in the same position as the +old component, unless one of the keywords `before` or `after` is specified. The component is +added with the same first and last values, unless the keywords `first` or `last` are specified. +Optional boolean argument `reconnect` with default value `true` indicates whether the existing +parameter connections should be maintained in the new component. """ -function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing, - reconnect::Bool=true) +@method function replace_comp!(obj::CompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing, + reconnect::Bool=true) - if ! hascomp(md, comp_name) + if ! hascomp(obj, comp_name) error("Cannot replace '$comp_name'; component not found in model.") end # Get original position if new before or after not specified if before === nothing && after === nothing - comps = collect(compkeys(md)) + comps = collect(compkeys(obj)) n = length(comps) if n > 1 idx = findfirst(isequal(comp_name), comps) @@ -767,7 +733,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end # Get original first and last if new run period not specified - old_comp = compdef(md, comp_name) + old_comp = compdef(obj, comp_name) first = first === nothing ? old_comp.first : first last = last === nothing ? old_comp.last : last @@ -783,7 +749,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end # Check incoming parameters - incoming_params = map(ipc -> ipc.dst_par_name, internal_param_conns(md, comp_name)) + incoming_params = map(ipc -> ipc.dst_par_name, internal_param_conns(obj, comp_name)) old_params = filter(pair -> pair.first in incoming_params, old_comp.parameters) new_params = new_comp.parameters if !_compare_datum(new_params, old_params) @@ -791,7 +757,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end # Check outgoing variables - outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_name == comp_name, internal_param_conns(md))) + outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_name == comp_name, internal_param_conns(obj))) old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) new_vars = new_comp.variables if !_compare_datum(new_vars, old_vars) @@ -800,7 +766,7 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com # Check external parameter connections remove = [] - for epc in external_param_conns(md, comp_name) + for epc in external_param_conns(obj, comp_name) param_name = epc.param_name if ! haskey(new_params, param_name) # TODO: is this the behavior we want? don't error in this case? just (warn)? @warn "Removing external parameter connection from component $comp_name; parameter $param_name no longer exists in component." @@ -816,17 +782,17 @@ function replace_comp!(md::ModelDef, comp_id::ComponentId, comp_name::Symbol=com end end end - filter!(epc -> !(epc in remove), external_param_conns(md)) + filter!(epc -> !(epc in remove), external_param_conns(obj)) # Delete the old component from comps_dict, leaving the existing parameter connections - delete!(md.ccd.comps_dict, comp_name) + delete!(obj.ccd.comps_dict, comp_name) else # Delete the old component and all its internal and external parameter connections - delete!(md, comp_name) + delete!(obj, comp_name) end # Re-add - add_comp!(md, comp_id, comp_name; first=first, last=last, before=before, after=after) + add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end """ diff --git a/src/core/instances.jl b/src/core/instances.jl index ae9e154e4..93d41457f 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -9,67 +9,27 @@ Return the `ModelDef` contained by ModelInstance `mi`. """ modeldef(mi::ModelInstance) = mi.md -compinstance(cci::CompositeComponentInstance, name::Symbol) = cci.comps_dict[name] -@delegate compinstance(mi::ModelInstance, name::Symbol) => cci - -has_component(cci::CompositeComponentInstance, name::Symbol) = haskey(cci.comps_dict, name) -@delegate has_component(mi::ModelInstance, name::Symbol) => cci - - -compdef(ci::ComponentInstance) = compdef(ci.comp_id) - -""" - nameof(ci::ComponentInstance) - -Return the name of the component `ci`. -""" -nameof(ci::ComponentInstance) = ci.comp_name - -compid(ci::ComponentInstance) = ci.comp_id -dims(ci::ComponentInstance) = ci.dim_dict -first_period(ci::ComponentInstance) = ci.first -last_period(ci::ComponentInstance) = ci.last - """ - components(mi::ModelInstance) + @method add_comp!(obj::CompositeComponentInstance, ci::absclass(ComponentInstance)) -Return an iterator over components in model instance `mi`. +Add the (leaf or composite) component `ci` to a composite's list of components, and add +the `first` and `last` of `mi` to the ends of the composite's `firsts` and `lasts` lists. """ -@delegate components(mi::ModelInstance) => cci -components(cci::CompositeComponentInstance) = values(cci.comps_dict) +@method function add_comp!(obj::CompositeComponentInstance, ci::absclass(ComponentInstance)) + obj.comps_dict[nameof(ci)] = ci -firsts(cci::CompositeComponentInstance) = cci.firsts -@delegate firsts(mi::ModelInstance) => cci -@delegate firsts(m::Model) => mi - -lasts(cci::CompositeComponentInstance) = cci.lasts -@delegate lasts(mi::ModelInstance) => cci -@delegate lasts(m::Model) => mi - -clocks(cci::CompositeComponentInstance) = cci.clocks -@delegate clocks(mi::ModelInstance) => cci -@delegate clocks(m::Model) => mi - -""" - add_comp!(mi::ModelInstance, ci::ComponentInstance) - -Add the (leaf or composite) component `ci` to the `ModelInstance` `mi`'s list of -components, and add the `first` and `last` of `mi` to the ends of the `firsts` and -`lasts` lists of `mi`, respectively. -""" -@delegate add_comp!(mi::ModelInstance, ci::ComponentInstance) => cci - -function add_comp!(cci::CompositeComponentInstance, ci::ComponentInstance) - cci.comps_dict[nameof(ci)] = ci - - push!(cci.firsts, first_period(ci)) - push!(cci.lasts, last_period(ci)) + push!(obj.firsts, first_period(ci)) + push!(obj.lasts, last_period(ci)) + nothing end # # Setting/getting parameter and variable values # +# +# TBD: once working, explore whether these can be methods of ComponentInstanceData{NT} +# # Get the object stored for the given variable, not the value of the variable. # This is used in the model building process to connect internal parameters. @inline function get_property_obj(obj::ComponentInstanceParameters{NT}, name::Symbol) where {NT} @@ -120,7 +80,7 @@ end Return the value of parameter `name` in (leaf or composite) component `ci`. """ -function get_param_value(ci::ComponentInstance, name::Symbol) +@method function get_param_value(ci::ComponentInstance, name::Symbol) try return getproperty(ci.parameters, name) catch err @@ -137,7 +97,7 @@ end Return the value of variable `name` in component `ci`. """ -function get_var_value(ci::ComponentInstance, name::Symbol) +@method function get_var_value(ci::ComponentInstance, name::Symbol) try # println("Getting $name from $(ci.variables)") return getproperty(ci.variables, name) @@ -150,58 +110,33 @@ function get_var_value(ci::ComponentInstance, name::Symbol) end end -set_param_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) - -set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) - -# Allow values to be obtained from either parameter type using one method name. -value(param::ScalarModelParameter) = param.value - -value(param::ArrayModelParameter) = param.values +@method set_param_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) -dimensions(obj::ArrayModelParameter) = obj.dimensions - -dimensions(obj::ScalarModelParameter) = [] +@method set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) """ - variables(mi::ModelInstance, comp_name::Symbol) + variables(obj::_CompositeComponentInstance_, comp_name::Symbol) -Return the `ComponentInstanceVariables` for `comp_name` in ModelInstance 'mi'. +Return the `ComponentInstanceVariables` for `comp_name` in CompositeComponentInstance `obj`. """ -variables(mi::ModelInstance, comp_name::Symbol) = variables(compinstance(mi, comp_name)) - -variables(ci::ComponentInstance) = ci.variables - -@delegate variables(mi::ModelInstance) => cci +@method variables(obj::CompositeComponentInstance, comp_name::Symbol) = variables(compinstance(obj, comp_name)) function variables(m::Model) - if m.mi === nothing - error("Must build model to access variables. Use variables(m.md) to get variable definitions.") + if ! is_built(m) + error("Must build model to access variable instances. Use variables(modeldef(m)) to get variable definitions.") end - return variables(m.mi) + return variables(modelinstance(m)) end """ - parameters(mi::ModelInstance, comp_name::Symbol) - -Return the `ComponentInstanceParameters` for `comp_name` in ModelInstance 'mi'. -""" -parameters(mi::ModelInstance, comp_name::Symbol) = parameters(compinstance(mi, comp_name)) - -""" - parameters(ci::ComponentInstance) + parameters(obj::_CompositeComponentInstance_, comp_name::Symbol) -Return an iterator over the parameters in `ci`. +Return the `ComponentInstanceParameters` for `comp_name` in CompositeComponentInstance `obj`. """ -parameters(ci::ComponentInstance) = ci.parameters - -@delegate parameters(m::Model) => mi - -@delegate parameters(mi::ModelInstance) => cci - +@method parameters(obj::CompositeComponentInstance, comp_name::Symbol) = parameters(compinstance(obj, comp_name)) function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) - if ! has_component(mi, comp_name) + if ! has_comp(mi, comp_name) error("Component :$comp_name does not exist in current model") end @@ -227,12 +162,13 @@ end Return the size of index `dim_name`` in model instance `mi`. """ -dim_count(mi::ModelInstance, dim_name::Symbol) = dim_count(mi.md, dim_name) +@delegate dim_count(mi::ModelInstance, dim_name::Symbol) => md -dim_key_dict(mi::ModelInstance) = dim_key_dict(mi.md) +@delegate dim_key_dict(mi::ModelInstance) => md -dim_value_dict(mi::ModelInstance) = dim_value_dict(mi.md) +@delegate dim_value_dict(mi::ModelInstance) => md +# TBD: make this a @method? function make_clock(mi::ModelInstance, ntimesteps, time_keys::Vector{Int}) last = time_keys[min(length(time_keys), ntimesteps)] @@ -247,7 +183,7 @@ function make_clock(mi::ModelInstance, ntimesteps, time_keys::Vector{Int}) end end -function reset_variables(ci::LeafComponentInstance) +@method function reset_variables(ci::ComponentInstance) # println("reset_variables($(ci.comp_id))") vars = ci.variables @@ -266,16 +202,14 @@ function reset_variables(ci::LeafComponentInstance) end end -function reset_variables(cci::CompositeComponentInstance) - for ci in components(cci) +@method function reset_variables(obj::CompositeComponentInstance) + for ci in components(obj) reset_variables(ci) end return nothing end -@delegate reset_variables(mi::ModelInstance) => cci - -function init(ci::LeafComponentInstance) +function init(ci::ComponentInstance) reset_variables(ci) if ci.init != nothing @@ -284,16 +218,14 @@ function init(ci::LeafComponentInstance) return nothing end -function init(cci::CompositeComponentInstance) - for ci in components(cci) +@method function init(obj::CompositeComponentInstance) + for ci in components(obj) init(ci) end return nothing end -@delegate init(mi::ModelInstance) => cci - -function run_timestep(ci::LeafComponentInstance, clock::Clock) +function run_timestep(ci::ComponentInstance, clock::Clock) if ci.run_timestep != nothing ci.run_timestep(parameters(ci), variables(ci), dims(ci), clock.ts) end @@ -304,8 +236,8 @@ function run_timestep(ci::LeafComponentInstance, clock::Clock) return nothing end -function run_timestep(cci::CompositeComponentInstance, clock::Clock) - for ci in components(cci) +@method function run_timestep(obj::CompositeComponentInstance, clock::Clock) + for ci in components(obj) run_timestep(ci, clock) end return nothing @@ -316,6 +248,8 @@ function _run_components(mi::ModelInstance, clock::Clock, comp_clocks::Vector{Clock{T}}) where {T <: AbstractTimestep} @info "_run_components: firsts: $firsts, lasts: $lasts" comp_instances = components(mi) + + # collect these since we iterate over them repeatedly below tups = collect(zip(comp_instances, firsts, lasts, comp_clocks)) while ! finished(clock) @@ -335,17 +269,17 @@ function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), error("Cannot run the model: no components have been created.") end - t::Vector{Int} = dimkeys === nothing ? dim_keys(mi.md, :time) : dimkeys[:time] + t::Vector{Int} = dimkeys === nothing ? dim_keys(mi, :time) : dimkeys[:time] firsts_vec = firsts(mi) lasts_vec = lasts(mi) if isuniform(t) - _, stepsize = first_and_step(t) + stepsize = step_size(t) comp_clocks = [Clock{FixedTimestep}(first, stepsize, last) for (first, last) in zip(firsts_vec, lasts_vec)] else comp_clocks = Array{Clock{VariableTimestep}}(undef, length(firsts_vec)) - for i = 1:length(firsts) + for i = 1:length(firsts_vec) first_index = findfirst(isequal(firsts_vec[i]), t) last_index = findfirst(isequal(lasts_vec[i]), t) times = (t[first_index:last_index]...,) diff --git a/src/core/model.jl b/src/core/model.jl index d231367ad..6f51f3ac5 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -12,21 +12,26 @@ Return the `ModelDef` contained by Model `m`. modeldef(m::Model) = m.md modelinstance(m::Model) = m.mi +modelinstance_def(m::Model) = modeldef(modelinstance(m)) + +is_built(m::Model) = (modelinstance(m) !== nothing) @delegate compinstance(m::Model, name::Symbol) => mi +@delegate has_comp(m::Model, name::Symbol) => mi -@delegate number_type(m::Model) => md +@delegate firsts(m::Model) => mi +@delegate lasts(m::Model) => mi +@delegate clocks(m::Model) => mi -@delegate external_param_conns(m::Model) => md +@delegate number_type(m::Model) => md @delegate internal_param_conns(m::Model) => md +@delegate external_param_conns(m::Model) => md @delegate external_params(m::Model) => md - @delegate external_param(m::Model, name::Symbol) => md @delegate connected_params(m::Model, comp_name::Symbol) => md - @delegate unconnected_params(m::Model) => md @delegate add_connector_comps(m::Model) => md @@ -153,7 +158,7 @@ end """ components(m::Model) -Return an iterator on the components in model `m`. +Return an iterator on the components in a model's model instance. """ @delegate components(m::Model) => mi @@ -229,18 +234,18 @@ Return a list of the parameter definitions for `comp_name` in model `m`. parameters(m::Model, comp_name::Symbol) = parameters(compdef(m, comp_name)) function variable(m::Model, comp_name::Symbol, var_name::Symbol) - comp_def = compdef(m, comp_name) - return comp_def.variables[var_name] + vars = variables(m, comp_name) + return vars[var_name] end function variable_unit(m::Model, comp_name::Symbol, var_name::Symbol) var = variable(m, comp_id, var_name) - return var.unit + return unit(var) end function variable_dimensions(m::Model, comp_name::Symbol, var_name::Symbol) var = variable(m, comp_id, var_name) - return var.dimensions + return dimensions(var) end """ @@ -295,12 +300,13 @@ function Base.run(m::Model; ntimesteps::Int=typemax(Int), error("Cannot run a model with no components.") end - if m.mi === nothing + if ! is_built(m) build(m) end # println("Running model...") - run(m.mi, ntimesteps, dim_keys) + mi = modelinstance(m) + run(mi, ntimesteps, dim_keys) nothing end \ No newline at end of file diff --git a/src/core/types.jl b/src/core/types.jl index 607083aad..f8f36d748 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -134,6 +134,13 @@ Base.convert(::Type{T}, s::ScalarModelParameter{T}) where {T} = T(s.value) ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(value)}(value, dims) +# Allow values to be obtained from either parameter type using one method name. +value(param::ArrayModelParameter) = param.values +value(param::ScalarModelParameter) = param.value + +dimensions(obj::ArrayModelParameter) = obj.dimensions +dimensions(obj::ScalarModelParameter) = [] + abstract type AbstractConnection end @@ -196,6 +203,8 @@ Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, comp_id::ComponentId end +comp_name(dr::DatumReference) = dr.comp_id.comp_name + # *Def implementation doesn't need to be performance-optimized since these # are used only to create *Instance objects that are used at run-time. With # this in mind, we don't create dictionaries of vars, params, or dims in the @@ -208,7 +217,7 @@ global const BindingTypes = Union{Int, Float64, DatumReference} @class DimensionDef <: NamedObj # Similar structure is used for variables and parameters (parameters merely adds `default`) -@class mutable DatumDef <: NamedObj begin +@class mutable DatumDef(getter_prefix="", setters=false) <: NamedObj begin datatype::DataType dimensions::Vector{Symbol} description::String @@ -222,7 +231,10 @@ end default::Any end -@class mutable ComponentDef <: NamedObj begin +# to allow getters to be created +import Base: first, last + +@class mutable ComponentDef(getter_prefix="") <: NamedObj begin comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} @@ -312,6 +324,11 @@ end end end +@method external_param(obj::CompositeComponentDef, name::Symbol) = obj.external_params[name] + +@method add_backup!(obj::CompositeComponentDef, backup) = push!(obj.backups, backup) + + @class mutable ModelDef <: CompositeComponentDef begin dimensions2::Dict{Symbol, Dimension} number_type::DataType @@ -334,7 +351,7 @@ end # # Supertype for variables and parameters in component instances -@class ComponentInstanceData{NT <: NamedTuple} begin +@class ComponentInstanceData(getters=false, setters=false){NT <: NamedTuple} begin nt::NT end @@ -356,7 +373,7 @@ end ComponentInstanceParameters(names, types, values) = _make_data_obj(ComponentInstanceParameters, names, types, values) ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInstanceVariables, names, types, values) -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin +@class mutable ComponentInstance(getters=false, setters=false){TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin comp_name::Symbol comp_id::ComponentId variables::TV @@ -380,7 +397,7 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst self.first = comp_def.first self.last = comp_def.last - comp_module = Base.eval(Main, comp_id.module_name) + comp_module = Main.eval(comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) # CompositeComponentInstances use a standard method that just loops over inner components. @@ -406,27 +423,28 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst return self end - function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = new{TV, TP}() - return ComponentInstance(self, comp_def, vars, pars, name) + # Create an empty instance with the given type parameters + function ComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + return new{TV, TP} end end +function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = ComponentInstance{TV, TP}() + return ComponentInstance(self, comp_def, vars, pars, name) +end + # These can be called on CompositeComponentInstances and ModelInstances -@method comp_name(obj::ComponentInstance) = obj.comp_name -@method comp_id(obj::ComponentInstance) = obj.comp_id -@method variables(obj::ComponentInstance) = obj.variables -@method parameters(obj::ComponentInstance) = obj.parameters -@method dim_dict(obj::ComponentInstance) = obj.dim_dict +@method compdef(obj::ComponentInstance) = compdef(comp_id(obj)) +@method dims(obj::ComponentInstance) = obj.dim_dict @method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_dict, name) @method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_dict[name] -@method Base.first(obj::ComponentInstance) = obj.first -@method Base.last(obj::ComponentInstance) = obj.last -@method init_func(obj::ComponentInstance) = obj.init -@method run_timestep_func(obj::ComponentInstance) = obj.run_timestep +@method first_period(obj::ComponentInstance) = obj.first +@method last_period(obj::ComponentInstance) = obj.last +@method first_and_last(obj::ComponentInstance) = (obj.first, obj.last) @class mutable CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: ComponentInstance begin @@ -467,12 +485,9 @@ end end # These methods can be called on ModelInstances as well -@method comp_dict(obj::CompositeComponentInstance) = obj.comp_dict +@method components(obj::CompositeComponentInstance) = values(comps_dict) @method has_comp(obj::CompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) -@method comp_instance(obj::CompositeComponentInstance, name::Symbol) = obj.comps_dict[name] -@method firsts(obj::CompositeComponentInstance) = obj.firsts -@method lasts(obj::CompositeComponentInstance) = obj.lasts -@method clocks(obj::CompositeComponentInstance) = obj.clocks +@method compinstance(obj::CompositeComponentInstance, name::Symbol) = obj.comps_dict[name] @method is_leaf(ci::ComponentInstance) = true @method is_leaf(ci::CompositeComponentInstance) = false diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index 72e3c8435..086b4d547 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -188,7 +188,7 @@ function _copy_mcs_params(mcs::MonteCarloSimulation) param_vec = Vector{Dict{Symbol, ModelParameter}}(undef, length(mcs.models)) for (i, m) in enumerate(mcs.models) - md = m.mi.md + md = modelinstance_def(m) param_vec[i] = Dict{Symbol, ModelParameter}(trans.paramname => copy(external_param(md, trans.paramname)) for trans in mcs.translist) end @@ -197,7 +197,8 @@ end function _restore_mcs_params!(mcs::MonteCarloSimulation, param_vec::Vector{Dict{Symbol, ModelParameter}}) for (m, params) in zip(mcs.models, param_vec) - md = m.mi.md + md = modelinstance_def(m) + for trans in mcs.translist name = trans.paramname param = params[name] @@ -285,7 +286,8 @@ function _perturb_params!(mcs::MonteCarloSimulation, trialnum::Int) trialdata = get_trial(mcs, trialnum) for m in mcs.models - md = m.mi.md + md = modelinstance_def(m) + for trans in mcs.translist param = external_param(md, trans.paramname) rvalue = getfield(trialdata, trans.rvname) @@ -372,7 +374,7 @@ function run_mcs(mcs::MonteCarloSimulation, end for m in mcs.models - if m.mi === nothing + if ! is_built(m) build(m) end end diff --git a/src/utils/getdataframe.jl b/src/utils/getdataframe.jl index a12f99d1c..f543a127a 100644 --- a/src/utils/getdataframe.jl +++ b/src/utils/getdataframe.jl @@ -7,8 +7,8 @@ Load a DataFrame from the variable or parameter `item_name` in component `comp_n nothing, a new DataFrame is allocated. Returns the populated DataFrame. """ function _load_dataframe(m::Model, comp_name::Symbol, item_name::Symbol, df::Union{Nothing,DataFrame}=nothing) - mi = m.mi - md = mi.md + mi = modelinstance(m) + md = modelinstance_def(m) dims = dimensions(m, comp_name, item_name) @@ -56,7 +56,7 @@ function _load_dataframe(m::Model, comp_name::Symbol, item_name::Symbol, df::Uni end function _df_helper(m::Model, comp_name::Symbol, item_name::Symbol, dims::Vector{Symbol}, data::AbstractArray) - md = m.md + md = modeldef(m) num_dims = length(dims) dim1name = dims[1] @@ -76,7 +76,8 @@ function _df_helper(m::Model, comp_name::Symbol, item_name::Symbol, dims::Vector df[dim2name] = repeat(keys2, outer = [len_dim1]) if dim1name == :time && size(data)[1] != len_dim1 #length(time_labels(md)) - ci = compinstance(m.mi, comp_name) + mi = modelinstance(m) + ci = compinstance(mi, comp_name) t = dimension(m, :time) first = t[ci.first] last = t[ci.last] @@ -91,7 +92,8 @@ function _df_helper(m::Model, comp_name::Symbol, item_name::Symbol, dims::Vector # shift the data to be padded with missings if this data is shorter than the model if dim1name == :time && size(data)[1] != len_dim1 #length(time_labels(md)) - ci = compinstance(m.mi, comp_name) + mi = modelinstance(m) + ci = compinstance(mi, comp_name) t = dimension(m, :time) first = t[ci.first] last = t[ci.last] @@ -174,7 +176,7 @@ Return the values for variable or parameter `item_name` in `comp_name` of model `m` as a DataFrame. """ function getdataframe(m::Model, comp_name::Symbol, item_name::Symbol) - if m.mi === nothing + if ! is_built(m) error("Cannot get DataFrame: model has not been built yet.") end diff --git a/src/utils/graph.jl b/src/utils/graph.jl index 2ad93bffa..361105962 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -60,9 +60,11 @@ function get_connections(m::Model, ci::ComponentInstance, which::Symbol) end function get_connections(m::Model, comp_name::Symbol, which::Symbol) - return _filter_connections(internal_param_conns(m.md), comp_name, which) + md = modeldef(m) + return _filter_connections(internal_param_conns(md), comp_name, which) end function get_connections(mi::ModelInstance, comp_name::Symbol, which::Symbol) - return _filter_connections(internal_param_conns(mi.md), comp_name, which) + md = modeldef(mi) + return _filter_connections(internal_param_conns(md), comp_name, which) end diff --git a/test/mcs/test_defmcs.jl b/test/mcs/test_defmcs.jl index 23f3a2e5d..9da12e39a 100644 --- a/test/mcs/test_defmcs.jl +++ b/test/mcs/test_defmcs.jl @@ -45,7 +45,8 @@ m = model # Optionally, user functions can be called just before or after a trial is run function print_result(m::Model, mcs::MonteCarloSimulation, trialnum::Int) - ci = Mimi.compinstance(m.mi, :emissions) + mi = Mimi.modelinstance(m) + ci = Mimi.compinstance(mi, :emissions) value = Mimi.get_variable_value(ci, :E_Global) println("$(ci.comp_id).E_Global: $value") end diff --git a/test/test_components.jl b/test/test_components.jl index 335bdb69b..cb1474cd7 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -130,7 +130,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model set_param!(m, :C, :par1, zeros(5)) Mimi.build(m) # Build the model -ci = compinstance(m.mi, :C) # Get the component instance +ci = compinstance(m, :C) # Get the component instance @test ci.first == 2001 # The component instance's first and last values should match the model's index @test ci.last == 2005 @@ -141,7 +141,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model update_param!(m, :par1, zeros(16); update_timesteps=true) Mimi.build(m) # Build the model -ci = compinstance(m.mi, :C) # Get the component instance +ci = compinstance(m, :C) # Get the component instance @test ci.first == 2005 # The component instance's first and last values should match the model's index @test ci.last == 2020 @@ -157,7 +157,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model set_param!(m, :C, :par1, zeros(81)) Mimi.build(m) # Build the model -ci = compinstance(m.mi, :C) # Get the component instance +ci = compinstance(m, :C) # Get the component instance @test ci.first == 2010 # The component instance's first and last values are the same as in the comp def @test ci.last == 2090 @@ -167,7 +167,7 @@ cd = compdef(m.md, :C) # Get the component definition in the model @test cd.last == 2090 Mimi.build(m) # Build the model -ci = compinstance(m.mi, :C) # Get the component instance +ci = compinstance(m, :C) # Get the component instance @test ci.first == 2010 # The component instance's first and last values are the same as the comp def @test ci.last == 2090 diff --git a/test/test_composite.jl b/test/test_composite.jl index cdc4d9974..a9be8e0bc 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -82,10 +82,10 @@ build(MyComposite) end # module +# TBD: remove once debugged m = TestComposite.m md = m.md ccd = md.ccd mi = m.mi -cci = mi.cci -nothing \ No newline at end of file +nothing From 4b7dad4307affe09fc882f18ffcfa42b9cbf6087 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 19 Dec 2018 17:14:11 -0800 Subject: [PATCH 24/81] Updated abstract class names --- src/core/types.jl | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/core/types.jl b/src/core/types.jl index 321250b44..5d3fec51b 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -248,7 +248,7 @@ import Base: first, last end # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef(self::_ComponentDef_, comp_id::Union{Nothing, ComponentId}=nothing; + function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; name::Union{Nothing, Symbol}=nothing) if name === nothing name = (comp_id === nothing ? gensym("anonymous") : comp_id.comp_name) @@ -274,7 +274,7 @@ import Base: first, last end @class mutable CompositeComponentDef <: ComponentDef begin - comps_dict::OrderedDict{Symbol, _ComponentDef_} + comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} exports::Vector{Pair{DatumReference, Symbol}} @@ -287,10 +287,10 @@ end sorted_comps::Union{Nothing, Vector{Symbol}} - function CompositeComponentDef(self::_CompositeComponentDef_, + function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::ComponentId, comps::Vector{T}, bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: _ComponentDef_} + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: AbstractComponentDef} comps_dict = OrderedDict{Symbol, T}([nameof(cd) => cd for cd in comps]) in_conns = Vector{InternalParameterConnection}() @@ -307,17 +307,17 @@ end function CompositeComponentDef(comp_id::ComponentId, comps::Vector{T}, bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: absclass(ComponentDef)} + exports::Vector{Pair{DatumReference, Symbol}}) where {T <: AbstractComponentDef} self = new() return CompositeComponentDef(self, comp_id, comps, bindings, exports) end - function CompositeComponentDef(self::Union{Nothing, absclass(CompositeComponentDef)}=nothing) + function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) self = (self === nothing ? new() : self) comp_id = ComponentId(:anonymous, :anonymous) # TBD: pass these in? - comps = Vector{absclass(ComponentDef)}() + comps = Vector{AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() return CompositeComponentDef(self, comp_id, comps, bindings, exports) @@ -333,7 +333,7 @@ end dimensions2::Dict{Symbol, Dimension} number_type::DataType - function ModelDef(self::_ModelDef_, number_type::DataType=Float64) + function ModelDef(self::AbstractModelDef, number_type::DataType=Float64) CompositeComponentDef(self) # call super's initializer dimensions = Dict{Symbol, Dimension}() @@ -384,7 +384,7 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst init::Union{Nothing, Function} run_timestep::Union{Nothing, Function} - function ComponentInstance(self::absclass(ComponentInstance), + function ComponentInstance(self::AbstractComponentInstance, comp_def::ComponentDef, vars::TV, pars::TP, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} @@ -448,15 +448,15 @@ end @class mutable CompositeComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: ComponentInstance begin - comps_dict::OrderedDict{Symbol, absclass(ComponentInstance)} + comps_dict::OrderedDict{Symbol, AbstractComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} clocks::Vector{Clock} - function CompositeComponentInstance(self::absclass(CompositeComponentInstance), - comps::Vector{absclass(ComponentInstance)}, + function CompositeComponentInstance(self::AbstractCompositeComponentInstance, + comps::Vector{AbstractComponentInstance}, comp_def::ComponentDef, name::Symbol=nameof(comp_def)) - comps_dict = OrderedDict{Symbol, absclass(ComponentInstance)}() + comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() firsts = Vector{Int}() lasts = Vector{Int}() clocks = Vector{Clock}() @@ -475,7 +475,7 @@ end end # Constructs types of vars and params from sub-components - function CompositeComponentInstance(comps::Vector{absclass(ComponentInstance)}, + function CompositeComponentInstance(comps::Vector{AbstractComponentInstance}, comp_def::ComponentDef, name::Symbol=nameof(comp_def)) (TV, TP) = _comp_instance_types(comps) @@ -494,11 +494,11 @@ end @method is_composite(ci::ComponentInstance) = !is_leaf(ci) # TBD: write these -function _comp_instance_types(comps::Vector{absclass(ComponentInstance)}) +function _comp_instance_types(comps::Vector{AbstractComponentInstance}) error("Need to define comp_instance_types") end -function _collect_vars_pars(comps::Vector{absclass(ComponentInstance)}) +function _collect_vars_pars(comps::Vector{AbstractComponentInstance}) error("Need to define comp_instance_types") end From 0be9e2c2cb08dd028d6f21b1e9f222491d79584b Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 16 Jan 2019 10:56:07 -0800 Subject: [PATCH 25/81] WIP - changes to support class-based version of composites --- REQUIRE | 1 + src/core/build.jl | 4 +- src/core/connections.jl | 18 ++-- src/core/defs.jl | 89 ++++++++++--------- src/core/instances.jl | 4 +- src/core/model.jl | 6 +- src/core/types.jl | 63 ++++++------- src/explorer/buildspecs.jl | 6 +- src/mcs/montecarlo.jl | 4 +- src/utils/getdataframe.jl | 6 +- test/test_model_structure.jl | 4 +- test/test_model_structure_variabletimestep.jl | 4 +- 12 files changed, 105 insertions(+), 104 deletions(-) diff --git a/REQUIRE b/REQUIRE index 0d82c14f7..2e6be38fd 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,5 @@ julia 1.0 +Classes Compose CSVFiles DataFrames diff --git a/src/core/build.jl b/src/core/build.jl index 27cd028d1..3f762f65b 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -3,7 +3,7 @@ connector_comp_name(i::Int) = Symbol("ConnectorComp$i") # Return the datatype to use for instance variables/parameters function _instance_datatype(md::ModelDef, def::absclass(DatumDef)) dtype = def.datatype == Number ? number_type(md) : datatype(def) - dims = dimensions(def) + dims = dim_names(def) num_dims = dim_count(def) if num_dims == 0 @@ -29,7 +29,7 @@ end # Create the Ref or Array that will hold the value(s) for a Parameter or Variable function _instantiate_datum(md::ModelDef, def::absclass(DatumDef)) dtype = _instance_datatype(md, def) - dims = dimensions(def) + dims = dim_names(def) num_dims = length(dims) # Scalar datum diff --git a/src/core/connections.jl b/src/core/connections.jl index d887949a0..4ab17837c 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -25,8 +25,8 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, error("Mismatched datatype of parameter connection: Component: $(comp_def.comp_id) ($t1), Parameter: $param_name ($t2)") end - comp_dims = dimensions(param_def) - param_dims = dimensions(ext_param) + comp_dims = dim_names(param_def) + param_dims = dim_names(ext_param) if ! isempty(param_dims) && size(param_dims) != size(comp_dims) d1 = size(comp_dims) @@ -45,7 +45,7 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, if isa(dim, Symbol) param_length = size(ext_param.values)[i] if dim == :time - t = dimensions(md)[:time] + t = dimension(md, :time) first = first_period(md, comp_def) last = last_period(md, comp_def) comp_length = t[last] - t[first] + 1 @@ -94,9 +94,9 @@ and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ function connect_param!(md::ModelDef, - dst_comp_name::Symbol, dst_par_name::Symbol, - src_comp_name::Symbol, src_var_name::Symbol, - backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + dst_comp_name::Symbol, dst_par_name::Symbol, + src_comp_name::Symbol, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) # remove any existing connections for this dst parameter disconnect_param!(md, dst_comp_name, dst_par_name) @@ -120,7 +120,7 @@ function connect_param!(md::ModelDef, # some other check for second dimension?? dst_param = parameter(dst_comp_def, dst_par_name) - dst_dims = dimensions(dst_param) + dst_dims = dim_names(dst_param) backup = convert(Array{number_type(md)}, backup) # converts number type and, if it's a NamedArray, it's converted to Array first = first_period(md, dst_comp_def) @@ -408,7 +408,7 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise # Check size of provided parameter if update_timesteps && param.values isa TimestepArray - expected_size = ([length(dim_keys(md, d)) for d in param.dimensions]...,) + expected_size = ([length(dim_keys(md, d)) for d in dim_names(param)]...,) else expected_size = size(param.values) end @@ -421,7 +421,7 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise T = eltype(value) N = length(size(value)) new_timestep_array = get_timestep_array(md, T, N, value) - set_external_param!(md, name, ArrayModelParameter(new_timestep_array, param.dimensions)) + set_external_param!(md, name, ArrayModelParameter(new_timestep_array, dim_names(param))) elseif raise_error error("Cannot update timesteps; parameter $name is not a TimestepArray.") diff --git a/src/core/defs.jl b/src/core/defs.jl index a7457956c..ee6302c69 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -85,13 +85,14 @@ end number_type(md::ModelDef) = md.number_type -numcomponents(obj::ComponentDef) = 0 # no sub-components +# TBD: should be numcomps() +@method numcomponents(obj::ComponentDef) = 0 # no sub-components @method numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) -function dump_components() +function dumpcomps() for comp in compdefs() println("\n$(nameof(comp))") - for (tag, objs) in ((:Variables, variables(comp)), (:Parameters, parameters(comp)), (:Dimensions, dimensions(comp))) + for (tag, objs) in ((:Variables, variables(comp)), (:Parameters, parameters(comp)), (:Dimensions, dim_dict(comp))) println(" $tag") for obj in objs println(" $(nameof(obj)) = $obj") @@ -148,16 +149,19 @@ end @method function add_dimension!(comp::ComponentDef, name) dim = (name isa Int) ? Dimension(name) : nothing - comp.dimensions[Symbol(name)] = dim - comp.dimensions[name] = DimensionDef(name) + comp.dim_dict[Symbol(name)] = dim # TBD: this makes no sense (test case of integer instead of symbol) + comp.dim_dict[name] = DimensionDef(name) end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -@method function dimensions(ccd::CompositeComponentDef) +# Allow our usual abbreviation +add_dim! = add_dimension! + +@method function dim_names(ccd::CompositeComponentDef) dims = Vector{DimensionDef}() for cd in compdefs(ccd) - append!(dims, dimensions(cd)) + append!(dims, keys(dim_dict(cd))) # TBD: this doesn't look right end # use Set to eliminate duplicates @@ -165,14 +169,17 @@ add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), na return collect(Set(dims)) end -@method dimensions(comp_def::ComponentDef, datum_name::Symbol) = dimensions(datumdef(comp_def, datum_name)) +@method dim_names(comp_def::ComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) -@method dim_count(def::DatumDef) = length(dimensions(def)) +@method dim_count(def::DatumDef) = length(dim_names(def)) function step_size(values::Vector{Int}) return length(values) > 1 ? values[2] - values[1] : 1 end +# +# TBD: should these be defined as @method of CompositeComponentDef +# function step_size(md::ModelDef) keys::Vector{Int} = time_labels(md) return step_size(keys) @@ -212,7 +219,7 @@ end # TBD: is this needed for composites? function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) - dims = dimensions(comp_def, datum_name) + dims = dim_names(comp_def, datum_name) if dims[1] == :time time_length = getspan(md, comp_def)[1] rest_dims = filter(x->x!=:time, dims) @@ -223,32 +230,32 @@ function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) return datum_size end -@method has_dim(obj::CompositeComponentDef, name::Symbol) = haskey(obj.dimensions, name) +@method has_dim(obj::CompositeComponentDef, name::Symbol) = haskey(obj.dim_dict, name) @method isuniform(obj::CompositeComponentDef) = obj.is_uniform @method set_uniform!(obj::CompositeComponentDef, value::Bool) = (obj.is_uniform = value) -@method dimension(obj::CompositeComponentDef, name::Symbol) = obj.dimensions[name] +@method dimension(obj::CompositeComponentDef, name::Symbol) = obj.dim_dict[name] -dimensions(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] +dim_names(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] -dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dimensions(md)]) -dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dimensions(md, dims)] +dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dim_dict(md)]) +dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(md, dims)] dim_count(md::ModelDef, name::Symbol) = length(dimension(md, name)) -dim_key_dict(md::ModelDef) = Dict([name => collect(keys(dim)) for (name, dim) in dimensions(md)]) +dim_key_dict(md::ModelDef) = Dict([name => collect(keys(dim)) for (name, dim) in dim_dict(md)]) dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) dim_values(md::ModelDef, name::Symbol) = collect(values(dimension(md, name))) -dim_value_dict(md::ModelDef) = Dict([name => collect(values(dim)) for (name, dim) in dimensions(md)]) +dim_value_dict(md::ModelDef) = Dict([name => collect(values(dim)) for (name, dim) in dim_dict(md)]) # Helper function invoked when the user resets the time dimension with set_dimension! # This function calls set_run_period! on each component definition to reset the first and last values. -function reset_run_periods!(md, first, last) - for comp_def in compdefs(md) +@method function _reset_run_periods!(ccd::CompositeComponentDef, first, last) + for comp_def in compdefs(ccd) changed = false first_per = first_period(comp_def) last_per = last_period(comp_def) @@ -289,7 +296,7 @@ an integer; or to the values in the vector or range if `keys` is either of those if name == :time set_uniform!(ccd, isuniform(keys)) if redefined - reset_run_periods!(ccd, k[1], k[end]) + _reset_run_periods!(ccd, keys[1], keys[end]) end end @@ -297,7 +304,7 @@ an integer; or to the values in the vector or range if `keys` is either of those end @method function set_dimension!(obj::CompositeComponentDef, name::Symbol, dim::Dimension) - obj.dimensions[name] = dim + obj.dim_dict[name] = dim end # helper functions used to determine if the provided time values are @@ -325,7 +332,7 @@ end # @method function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) - p = ParameterDef(name, datatype, dimensions, description, unit, :parameter, default) + p = ParameterDef(name, datatype, dimensions, description, unit, default) comp_def.parameters[name] = p return p end @@ -339,7 +346,7 @@ end Return a list of the parameter definitions for `comp_def`. """ -parameters(obj::ComponentDef) = values(obj.parameters) +@method parameters(obj::ComponentDef) = values(obj.parameters) @method function parameters(ccd::CompositeComponentDef) pars = ccd.parameters @@ -374,7 +381,7 @@ Return a list of all parameter names for a given component `comp_name` in a mode parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) #parameter_names(comp_def::ComponentDef) = [nameof(param) for param in parameters(comp_def)] -parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) +@method parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) @method parameter(obj::CompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) @@ -395,9 +402,9 @@ end return param.unit end -function parameter_dimensions(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) +@method function parameter_dimensions(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) param = parameter(obj, comp_name, param_name) - return param.dimensions + return dim_names(param) end """ @@ -470,9 +477,9 @@ end # # Variables # -variables(comp_def::ComponentDef) = values(comp_def.variables) +@method variables(comp_def::ComponentDef) = values(comp_def.variables) -function variables(ccd::CompositeComponentDef) +@method function variables(ccd::CompositeComponentDef) vars = ccd.variables # return cached variables, if any @@ -488,13 +495,11 @@ function variables(ccd::CompositeComponentDef) return values(vars) end -@delegate variables(md::ModelDef) => ccd - variables(comp_id::ComponentId) = variables(compdef(comp_id)) variables(dr::DatumReference) = variables(dr.comp_id) -function variable(comp_def::ComponentDef, var_name::Symbol) +@method function variable(comp_def::ComponentDef, var_name::Symbol) if is_composite(comp_def) variables(comp_def) # make sure values have been gathered end @@ -512,7 +517,7 @@ variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(m variable(dr::DatumReference) = variable(compdef(dr.comp_id), nameof(dr)) -has_variable(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.variables, name) +@method has_variable(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.variables, name) """ variable_names(md::ModelDef, comp_name::Symbol) @@ -531,13 +536,13 @@ end function variable_dimensions(md::ModelDef, comp_name::Symbol, var_name::Symbol) var = variable(md, comp_name, var_name) - return var.dimensions + return dim_names(var) end # Add a variable to a ComponentDef. CompositeComponents have no vars of their own, # only references to vars in components contained within. function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) - var_def = DatumDef(name, datatype, dimensions, description, unit, :variable) + var_def = VariableDef(name, datatype, dimensions, description, unit) comp_def.variables[name] = var_def return var_def end @@ -590,12 +595,12 @@ end const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} -@method function _append_comp!(obj::CompositeComponentDef, comp_name::Symbol, comp_def::ComponentDef) +@method function _append_comp!(obj::CompositeComponentDef, comp_name::Symbol, comp_def::AbstractComponentDef) obj.comps_dict[comp_name] = comp_def end -function _add_anonymous_dims!(md::ModelDef, comp_def::ComponentDef) - for (name, dim) in filter(pair -> pair[2] !== nothing, comp_def.dimensions) +function _add_anonymous_dims!(md::ModelDef, comp_def::AbstractComponentDef) + for (name, dim) in filter(pair -> pair[2] !== nothing, comp_def.dim_dict) @info "Setting dimension $name to $dim" set_dimension!(md, name, dim) end @@ -608,7 +613,7 @@ Add the component indicated by `comp_def` to the composite components indicated is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -@method function add_comp!(obj::CompositeComponentDef, comp_def::ComponentDef, comp_name::Symbol; +@method function add_comp!(obj::CompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -645,7 +650,7 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be set_run_period!(comp_def, first, last) - _add_anonymous_dims!(md, comp_def) + _add_anonymous_dims!(obj, comp_def) if before === nothing && after === nothing _append_comp!(obj, comp_name, comp_def) # just add it to the end @@ -753,8 +758,8 @@ parameter connections should be maintained in the new component. new_comp = compdef(comp_id) function _compare_datum(dict1, dict2) - set1 = Set([(k, v.datatype, v.dimensions) for (k, v) in dict1]) - set2 = Set([(k, v.datatype, v.dimensions) for (k, v) in dict2]) + set1 = Set([(k, v.datatype, v.dim_names) for (k, v) in dict1]) + set2 = Set([(k, v.datatype, v.dim_names) for (k, v) in dict2]) return set1 >= set2 end @@ -784,7 +789,7 @@ parameter connections should be maintained in the new component. else old_p = old_comp.parameters[param_name] new_p = new_params[param_name] - if new_p.dimensions != old_p.dimensions + if new_p.dim_names != old_p.dim_names error("Cannot replace and reconnect; parameter $param_name in new component has different dimensions.") end if new_p.datatype != old_p.datatype diff --git a/src/core/instances.jl b/src/core/instances.jl index 93d41457f..d2c664638 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -115,7 +115,7 @@ end @method set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) """ - variables(obj::_CompositeComponentInstance_, comp_name::Symbol) + variables(obj::AbstractCompositeComponentInstance, comp_name::Symbol) Return the `ComponentInstanceVariables` for `comp_name` in CompositeComponentInstance `obj`. """ @@ -129,7 +129,7 @@ function variables(m::Model) end """ - parameters(obj::_CompositeComponentInstance_, comp_name::Symbol) + parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) Return the `ComponentInstanceParameters` for `comp_name` in CompositeComponentInstance `obj`. """ diff --git a/src/core/model.jl b/src/core/model.jl index 6f51f3ac5..19bb96a7f 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -194,12 +194,12 @@ end datumdef(m::Model, comp_name::Symbol, item::Symbol) = datumdef(compdef(m.md, comp_name), item) """ - dimensions(m::Model, comp_name::Symbol, datum_name::Symbol) + dim_names(m::Model, comp_name::Symbol, datum_name::Symbol) Return the dimension names for the variable or parameter `datum_name` in the given component `comp_name` in model `m`. """ -dimensions(m::Model, comp_name::Symbol, datum_name::Symbol) = dimensions(compdef(m, comp_name), datum_name) +dim_names(m::Model, comp_name::Symbol, datum_name::Symbol) = dim_names(compdef(m, comp_name), datum_name) @delegate dimension(m::Model, dim_name::Symbol) => md @@ -245,7 +245,7 @@ end function variable_dimensions(m::Model, comp_name::Symbol, var_name::Symbol) var = variable(m, comp_id, var_name) - return dimensions(var) + return dim_names(var) end """ diff --git a/src/core/types.jl b/src/core/types.jl index 5d3fec51b..97a9ce566 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -119,7 +119,7 @@ end mutable struct ArrayModelParameter{T} <: ModelParameter values::T - dimensions::Vector{Symbol} # if empty, we don't have the dimensions' name information + dim_names::Vector{Symbol} # if empty, we don't have the dimensions' name information function ArrayModelParameter{T}(values::T, dims::Vector{Symbol}) where T new(values, dims) @@ -138,8 +138,8 @@ ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(va value(param::ArrayModelParameter) = param.values value(param::ScalarModelParameter) = param.value -dimensions(obj::ArrayModelParameter) = obj.dimensions -dimensions(obj::ScalarModelParameter) = [] +dim_names(obj::ArrayModelParameter) = obj.dim_names +dim_names(obj::ScalarModelParameter) = [] abstract type AbstractConnection end @@ -217,9 +217,9 @@ global const BindingTypes = Union{Int, Float64, DatumReference} @class DimensionDef <: NamedObj # Similar structure is used for variables and parameters (parameters merely adds `default`) -@class mutable DatumDef(getter_prefix="", setters=false) <: NamedObj begin +@class mutable DatumDef <: NamedObj begin datatype::DataType - dimensions::Vector{Symbol} + dim_names::Vector{Symbol} description::String unit::String end @@ -231,14 +231,11 @@ end default::Any end -# to allow getters to be created -import Base: first, last - -@class mutable ComponentDef(getter_prefix="") <: NamedObj begin +@class mutable ComponentDef <: NamedObj begin comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} - dimensions::OrderedDict{Symbol, Union{Nothing, DimensionDef}} + dim_dict::OrderedDict{Symbol, Union{Nothing, DimensionDef}} # should value be Dimension rather than DimensionDef? first::Union{Nothing, Int} last::Union{Nothing, Int} is_uniform::Bool @@ -258,7 +255,7 @@ import Base: first, last self.comp_id = comp_id self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() - self.dimensions = OrderedDict{Symbol, Union{Nothing, DimensionDef}}() + self.dim_dict = OrderedDict{Symbol, Union{Nothing, DimensionDef}}() self.first = self.last = nothing self.is_uniform = true return self @@ -273,6 +270,12 @@ import Base: first, last end end +@method comp_id(obj::ComponentDef) = obj.comp_id +@method dim_dict(obj::ComponentDef) = obj.dim_dict +@method first_period(obj::ComponentDef) = obj.first +@method last_period(obj::ComponentDef) = obj.last +@method isuniform(obj::ComponentDef) = obj.is_uniform + @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Pair{DatumReference, BindingTypes}} @@ -330,18 +333,11 @@ end @class mutable ModelDef <: CompositeComponentDef begin - dimensions2::Dict{Symbol, Dimension} number_type::DataType - - function ModelDef(self::AbstractModelDef, number_type::DataType=Float64) - CompositeComponentDef(self) # call super's initializer - - dimensions = Dict{Symbol, Dimension}() - return ModelDef(self, dimensions, number_type) - end function ModelDef(number_type::DataType=Float64) self = new() + CompositeComponentDef(self) # call super's initializer return ModelDef(self, number_type) end end @@ -351,7 +347,7 @@ end # # Supertype for variables and parameters in component instances -@class ComponentInstanceData(getters=false, setters=false){NT <: NamedTuple} begin +@class ComponentInstanceData{NT <: NamedTuple} begin nt::NT end @@ -367,13 +363,13 @@ function _make_data_obj(subclass::DataType, names, types, values) _make_data_obj(subclass, NT(values)) end -@class ComponentInstanceParameters{NT <: NamedTuple} <: ComponentInstanceData -@class ComponentInstanceVariables{NT <: NamedTuple} <: ComponentInstanceData +@class ComponentInstanceParameters <: ComponentInstanceData +@class ComponentInstanceVariables <: ComponentInstanceData ComponentInstanceParameters(names, types, values) = _make_data_obj(ComponentInstanceParameters, names, types, values) ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInstanceVariables, names, types, values) -@class mutable ComponentInstance(getters=false, setters=false){TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin comp_name::Symbol comp_id::ComponentId variables::TV @@ -443,11 +439,10 @@ end @method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_dict, name) @method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_dict[name] @method first_period(obj::ComponentInstance) = obj.first -@method last_period(obj::ComponentInstance) = obj.last +@method mutable(obj::ComponentInstance) = obj.last @method first_and_last(obj::ComponentInstance) = (obj.first, obj.last) -@class mutable CompositeComponentInstance{TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} <: ComponentInstance begin +@class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} firsts::Vector{Int} # in order corresponding with components lasts::Vector{Int} @@ -485,7 +480,7 @@ end end # These methods can be called on ModelInstances as well -@method components(obj::CompositeComponentInstance) = values(comps_dict) +@method components(obj::CompositeComponentInstance) = values(obj.comps_dict) @method has_comp(obj::CompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) @method compinstance(obj::CompositeComponentInstance, name::Symbol) = obj.comps_dict[name] @@ -515,18 +510,18 @@ Base.getproperty(dimdict::DimDict, property::Symbol) = getfield(dimdict, :dict)[ # ModelInstance holds the built model that is ready to be run -@class ModelInstance{TV <: ComponentInstanceVariables, - TP <: ComponentInstanceParameters} <: CompositeComponentInstance begin +@class ModelInstance <: CompositeComponentInstance begin md::ModelDef # similar to generated constructor, but taking TV and TP from superclass instance - function ModelInstance(md::ModelDef, s::CompositeComponentInstance{TV, TP}) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + # function ModelInstance(md::ModelDef, s::CompositeComponentInstance{TV, TP}) where + # {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - new{TV, TP}(s.comp_name, s.comp_id, s.variables, s.parameters, s.dim_dict, s.first, s.last, - s.init, s.run_timestep, s.comps_dict, s.firsts, s.lasts, s.clocks, md) - end + # new{TV, TP}(s.comp_name, s.comp_id, s.variables, s.parameters, s.dim_dict, s.first, s.last, + # s.init, s.run_timestep, s.comps_dict, s.firsts, s.lasts, s.clocks, md) + # end end + # # 6. User-facing Model types providing a simplified API to model definitions and instances. # diff --git a/src/explorer/buildspecs.jl b/src/explorer/buildspecs.jl index ddba9b97d..e880727ab 100644 --- a/src/explorer/buildspecs.jl +++ b/src/explorer/buildspecs.jl @@ -2,13 +2,13 @@ using Dates function dataframe_or_scalar(m::Model, comp_name::Symbol, item_name::Symbol) - dims = dimensions(m, comp_name, item_name) + dims = dim_names(m, comp_name, item_name) return length(dims) > 0 ? getdataframe(m, comp_name, item_name) : m[comp_name, item_name] end # Generate the VegaLite spec for a variable or parameter function _spec_for_item(m::Model, comp_name::Symbol, item_name::Symbol) - dims = dimensions(m, comp_name, item_name) + dims = dim_names(m, comp_name, item_name) # Control flow logic selects the correct plot type based on dimensions # and dataframe fields @@ -48,7 +48,7 @@ function _spec_for_item(m::Model, comp_name::Symbol, item_name::Symbol) end function _menu_item(m::Model, comp_name::Symbol, item_name::Symbol) - dims = dimensions(m, comp_name, item_name) + dims = dim_names(m, comp_name, item_name) if length(dims) == 0 value = m[comp_name, item_name] diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index 7a980bb35..4ea2c389f 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -40,7 +40,7 @@ function _store_param_results(m::Model, datum_key::Tuple{Symbol, Symbol}, trialn @debug "\nStoring trial results for $datum_key" (comp_name, datum_name) = datum_key - dims = dimensions(m, comp_name, datum_name) + dims = dim_names(m, comp_name, datum_name) if length(dims) == 0 # scalar value value = m[comp_name, datum_name] @@ -221,7 +221,7 @@ function _restore_param!(param::ArrayModelParameter{T}, name::Symbol, md::ModelD end function _param_indices(param::ArrayModelParameter{T}, md::ModelDef, trans::TransformSpec) where T - pdims = dimensions(param) # returns [] for scalar parameters + pdims = dim_names(param) # returns [] for scalar parameters num_pdims = length(pdims) tdims = trans.dims diff --git a/src/utils/getdataframe.jl b/src/utils/getdataframe.jl index f543a127a..3049791c1 100644 --- a/src/utils/getdataframe.jl +++ b/src/utils/getdataframe.jl @@ -10,7 +10,7 @@ function _load_dataframe(m::Model, comp_name::Symbol, item_name::Symbol, df::Uni mi = modelinstance(m) md = modelinstance_def(m) - dims = dimensions(m, comp_name, item_name) + dims = dim_names(m, comp_name, item_name) # Create a new df if one was not passed in df = df === nothing ? DataFrame() : df @@ -140,11 +140,11 @@ dimensions, which are used to join the respective item values. """ function getdataframe(m::Model, pairs::Pair{Symbol, Symbol}...) (comp_name1, item_name1) = pairs[1] - dims = dimensions(m, comp_name1, item_name1) + dims = dim_names(m, comp_name1, item_name1) df = getdataframe(m, comp_name1, item_name1) for (comp_name, item_name) in pairs[2:end] - next_dims = dimensions(m, comp_name, item_name) + next_dims = dim_names(m, comp_name, item_name) if dims != next_dims error("Can't create DataFrame from items with different dimensions ($comp_name1.$item_name1: $dims vs $comp_name.$item_name: $next_dims)") end diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 284e0e078..17439a221 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -108,8 +108,8 @@ time = dimension(m, :time) a = collect(keys(time)) @test all([a[i] == 2010 + 5*i for i in 1:18]) -@test dimensions(m, :A, :varA)[1] == :time -@test length(dimensions(m, :A, :parA)) == 0 +@test dim_names(m, :A, :varA)[1] == :time +@test length(dim_names(m, :A, :parA)) == 0 ################################ # tests for delete! function # diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 130903b89..f69a712b5 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -109,8 +109,8 @@ time = dimension(m, :time) a = collect(keys(time)) @test all([a[i] == years[i] for i in 1:28]) -@test dimensions(m, :A, :varA)[1] == :time -@test length(dimensions(m, :A, :parA)) == 0 +@test dim_names(m, :A, :varA)[1] == :time +@test length(dim_names(m, :A, :parA)) == 0 ################################ # tests for delete! function # From 414e3f188752392e9cfda5793272def6840021e8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 29 Jan 2019 07:40:35 -0800 Subject: [PATCH 26/81] WIP - debugging composite components implementation --- src/core/build.jl | 12 ++-- src/core/connections.jl | 9 +-- src/core/defcomposite.jl | 4 +- src/core/defs.jl | 32 ++++++---- src/core/types.jl | 130 ++++++++++++++++++++++++--------------- test/test_composite.jl | 23 ++++--- 6 files changed, 124 insertions(+), 86 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 3f762f65b..e4fabc616 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -2,7 +2,7 @@ connector_comp_name(i::Int) = Symbol("ConnectorComp$i") # Return the datatype to use for instance variables/parameters function _instance_datatype(md::ModelDef, def::absclass(DatumDef)) - dtype = def.datatype == Number ? number_type(md) : datatype(def) + dtype = def.datatype == Number ? number_type(md) : def.datatype dims = dim_names(def) num_dims = dim_count(def) @@ -74,7 +74,7 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) end # Create ComponentInstanceVariables for a composite component from the list of exported vars -function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) +@method function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) names = [] values = [] @@ -91,7 +91,7 @@ function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{ return ComponentInstanceVariables(Tuple(names), Tuple{types...}, Tuple(values)) end -function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +@method function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) names = [] values = [] @@ -129,7 +129,7 @@ end @info "_instantiate_vars composite $comp_name" for cd in compdefs(comp_def) - _instantiate_vars(md, cd, var_dict, par_dict) + _instantiate_vars(cd, md, var_dict, par_dict) end var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) @info "composite vars for $comp_name: $v " @@ -165,7 +165,7 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing # Make the external parameter connections for the hidden ConnectorComps. # Connect each :input2 to its associated backup value. - for (i, backup) in enumerate(backups(comp_def)) + for (i, backup) in enumerate(comp_def.backups) conn_comp_name = connector_comp_name(i) param = external_param(comp_def, backup) comp_pars = par_dict[conn_comp_name] @@ -247,7 +247,7 @@ function _build(md::ModelDef) @info "par_dict: $par_dict" ci = _build(md, var_dict, par_dict) - mi = ModelInstance(md, ci) + mi = ModelInstance(ci, md) _save_dim_dict_reference(mi) return mi end diff --git a/src/core/connections.jl b/src/core/connections.jl index 4ab17837c..f863d4a56 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -20,7 +20,7 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, param_def = parameter(comp_def, param_name) t1 = eltype(ext_param.values) - t2 = eltype(datatype(param_def)) + t2 = eltype(param_def.datatype) if !(t1 <: t2) error("Mismatched datatype of parameter connection: Component: $(comp_def.comp_id) ($t1), Parameter: $param_name ($t2)") end @@ -150,9 +150,10 @@ function connect_param!(md::ModelDef, else # If backup not provided, make sure the source component covers the span of the destination component - src_first, src_last = first_and_last(md, src_comp_def) - dst_first, dst_last = first_and_last(md, dst_comp_def) - if dst_first < src_first || dst_last > src_last + src_first, src_last = first_and_last(src_comp_def) + dst_first, dst_last = first_and_last(dst_comp_def) + if (dst_first !== nothing && src_first !== nothing && dst_first < src_first) || + (dst_last !== nothing && src_last !== nothing && dst_last > src_last) error("""Cannot connect parameter: $src_comp_name runs only from $src_first to $src_last, whereas $dst_comp_name runs from $dst_first to $dst_last. Backup data must be provided for missing years. Try calling: diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 59024590e..d0b28f2fe 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -80,8 +80,8 @@ macro defcomposite(cc_name, ex) result = :( # @__MODULE__ is evaluated in calling module when macro is interpreted - let calling_module = @__MODULE__ #, comp_mod_name = nothing - global $cc_name = CompositeComponentDef() + let calling_module = @__MODULE__ # Use __module__? + global $cc_name = CompositeComponentDef(calling_module) end ) diff --git a/src/core/defs.jl b/src/core/defs.jl index ee6302c69..c12e7d8a1 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -148,9 +148,10 @@ end # @method function add_dimension!(comp::ComponentDef, name) + # generally, we add dimension name with nothing instead of a Dimension instance, + # but in the case of an Int name, we create the "anonymous" dimension on the fly. dim = (name isa Int) ? Dimension(name) : nothing - comp.dim_dict[Symbol(name)] = dim # TBD: this makes no sense (test case of integer instead of symbol) - comp.dim_dict[name] = DimensionDef(name) + comp.dim_dict[Symbol(name)] = dim # TBD: test this end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) @@ -159,14 +160,12 @@ add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), na add_dim! = add_dimension! @method function dim_names(ccd::CompositeComponentDef) - dims = Vector{DimensionDef}() + dims = OrderedSet{Symbol}() # use a set to eliminate duplicates for cd in compdefs(ccd) - append!(dims, keys(dim_dict(cd))) # TBD: this doesn't look right + union!(dims, keys(dim_dict(cd))) # TBD: test this end - # use Set to eliminate duplicates - # TBD: If order is unimportant, store as a set instead? - return collect(Set(dims)) + return collect(dims) end @method dim_names(comp_def::ComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) @@ -194,6 +193,8 @@ function first_and_step(values::Vector{Int}) return values[1], step_size(values) end +@method first_and_last(obj::ComponentDef) = (obj.first, obj.last) + function time_labels(md::ModelDef) keys::Vector{Int} = dim_keys(md, :time) return keys @@ -230,7 +231,8 @@ function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) return datum_size end -@method has_dim(obj::CompositeComponentDef, name::Symbol) = haskey(obj.dim_dict, name) +# Symbols are added to the dim_dict in @defcomp (with value of nothing), but are set later using set_dimension! +@method has_dim(obj::CompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) @method isuniform(obj::CompositeComponentDef) = obj.is_uniform @@ -327,10 +329,13 @@ function isuniform(values::Int) return true end -# +# # Parameters # +# Callable on both ParameterDef and VariableDef +@method dim_names(obj::DatumDef) = obj.dim_names + @method function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, datatype, dimensions, description, unit, default) comp_def.parameters[name] = p @@ -417,7 +422,7 @@ they match the model's index labels. """ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) # perform possible dimension and labels checks - if isa(value, NamedArray) + if value isa NamedArray dims = dimnames(value) end @@ -429,7 +434,8 @@ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, num_dims = length(comp_param_dims) comp_def = compdef(md, comp_name) - data_type = datatype(parameter(comp_def, param_name)) + param = parameter(comp_def, param_name) + data_type = param.datatype dtype = data_type == Number ? number_type(md) : data_type if length(comp_param_dims) > 0 @@ -584,8 +590,8 @@ end end @method function set_run_period!(comp_def::ComponentDef, first, last) - first!(comp_def, first) - last!(comp_def, last) + comp_def.first = first + comp_def.last = last return nothing end diff --git a/src/core/types.jl b/src/core/types.jl index 97a9ce566..0fad1e858 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -194,7 +194,7 @@ end nameof(obj::NamedDef) = obj.name Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, -`CompositeComponentDef`, `DatumReference` and `DimensionDef`. +`CompositeComponentDef`, and `DatumReference`. """ @method Base.nameof(obj::NamedObj) = obj.name @@ -214,8 +214,6 @@ comp_name(dr::DatumReference) = dr.comp_id.comp_name global const BindingTypes = Union{Int, Float64, DatumReference} -@class DimensionDef <: NamedObj - # Similar structure is used for variables and parameters (parameters merely adds `default`) @class mutable DatumDef <: NamedObj begin datatype::DataType @@ -235,7 +233,7 @@ end comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} - dim_dict::OrderedDict{Symbol, Union{Nothing, DimensionDef}} # should value be Dimension rather than DimensionDef? + dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} first::Union{Nothing, Int} last::Union{Nothing, Int} is_uniform::Bool @@ -248,14 +246,14 @@ end function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; name::Union{Nothing, Symbol}=nothing) if name === nothing - name = (comp_id === nothing ? gensym("anonymous") : comp_id.comp_name) + name = (comp_id === nothing ? gensym(:anonymous) : comp_id.comp_name) end NamedObj(self, name) self.comp_id = comp_id self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() - self.dim_dict = OrderedDict{Symbol, Union{Nothing, DimensionDef}}() + self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.first = self.last = nothing self.is_uniform = true return self @@ -291,11 +289,12 @@ end sorted_comps::Union{Nothing, Vector{Symbol}} function CompositeComponentDef(self::AbstractCompositeComponentDef, - comp_id::ComponentId, comps::Vector{T}, + comp_id::ComponentId, + comps::Vector{<: AbstractComponentDef}, bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: AbstractComponentDef} + exports::Vector{Pair{DatumReference, Symbol}}) - comps_dict = OrderedDict{Symbol, T}([nameof(cd) => cd for cd in comps]) + comps_dict = OrderedDict{Symbol, AbstractComponentDef}([nameof(cd) => cd for cd in comps]) in_conns = Vector{InternalParameterConnection}() ex_conns = Vector{ExternalParameterConnection}() ex_params = Dict{Symbol, ModelParameter}() @@ -308,9 +307,9 @@ end return self end - function CompositeComponentDef(comp_id::ComponentId, comps::Vector{T}, + function CompositeComponentDef(comp_id::ComponentId, comps::Vector{<: AbstractComponentDef}, bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) where {T <: AbstractComponentDef} + exports::Vector{Pair{DatumReference, Symbol}}) self = new() return CompositeComponentDef(self, comp_id, comps, bindings, exports) @@ -320,17 +319,23 @@ end self = (self === nothing ? new() : self) comp_id = ComponentId(:anonymous, :anonymous) # TBD: pass these in? - comps = Vector{AbstractComponentDef}() + comps = Vector{T where T <: AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() return CompositeComponentDef(self, comp_id, comps, bindings, exports) end end +@method internal_param_conns(obj::CompositeComponentDef) = obj.internal_param_conns +@method external_param_conns(obj::CompositeComponentDef) = obj.external_param_conns + @method external_param(obj::CompositeComponentDef, name::Symbol) = obj.external_params[name] @method add_backup!(obj::CompositeComponentDef, backup) = push!(obj.backups, backup) +@method is_leaf(c::ComponentDef) = true +@method is_leaf(c::CompositeComponentDef) = false +@method is_composite(c::ComponentDef) = !is_leaf(c) @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType @@ -356,18 +361,18 @@ end @method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) @method Base.values(obj::ComponentInstanceData) = values(nt(obj)) -_make_data_obj(subclass::DataType, nt::NT) where {NT <: NamedTuple} = subclass{NT}(nt) +@class ComponentInstanceParameters <: ComponentInstanceData +@class ComponentInstanceVariables <: ComponentInstanceData -function _make_data_obj(subclass::DataType, names, types, values) +function ComponentInstanceParameters(names, types, values) NT = NamedTuple{names, types} - _make_data_obj(subclass, NT(values)) + return ComponentInstanceParameters{NT}(NT(values)) end -@class ComponentInstanceParameters <: ComponentInstanceData -@class ComponentInstanceVariables <: ComponentInstanceData - -ComponentInstanceParameters(names, types, values) = _make_data_obj(ComponentInstanceParameters, names, types, values) -ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInstanceVariables, names, types, values) +function ComponentInstanceVariables(names, types, values) + NT = NamedTuple{names, types} + return ComponentInstanceVariables{NT}(NT(values)) +end @class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin comp_name::Symbol @@ -381,7 +386,8 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst run_timestep::Union{Nothing, Function} function ComponentInstance(self::AbstractComponentInstance, - comp_def::ComponentDef, vars::TV, pars::TP, + comp_def::AbstractComponentDef, + vars::TV, pars::TP, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} @@ -393,6 +399,7 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst self.first = comp_def.first self.last = comp_def.last + @info "ComponentInstance evaluating $(comp_id.module_name)" comp_module = Main.eval(comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) @@ -421,12 +428,12 @@ ComponentInstanceVariables(names, types, values) = _make_data_obj(ComponentInst # Create an empty instance with the given type parameters function ComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - return new{TV, TP} + return new{TV, TP}() end end -function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, - name::Symbol=nameof(comp_def)) where +@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = ComponentInstance{TV, TP}() @@ -440,20 +447,20 @@ end @method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_dict[name] @method first_period(obj::ComponentInstance) = obj.first @method mutable(obj::ComponentInstance) = obj.last -@method first_and_last(obj::ComponentInstance) = (obj.first, obj.last) @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} - firsts::Vector{Int} # in order corresponding with components - lasts::Vector{Int} + firsts::Vector{Union{Nothing, Int}} # in order corresponding with components + lasts::Vector{Union{Nothing, Int}} clocks::Vector{Clock} function CompositeComponentInstance(self::AbstractCompositeComponentInstance, - comps::Vector{AbstractComponentInstance}, - comp_def::ComponentDef, name::Symbol=nameof(comp_def)) + comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractComponentDef, + name::Symbol=nameof(comp_def)) comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() - firsts = Vector{Int}() - lasts = Vector{Int}() + firsts = Vector{Union{Nothing, Int}}() + lasts = Vector{Union{Nothing, Int}}() clocks = Vector{Clock}() for ci in comps @@ -463,18 +470,18 @@ end # push!(clocks, ?) end - (vars, pars) = _collect_vars_pars(comps) + (vars, pars) = _comp_instance_vars_pars(comps) ComponentInstance(self, comp_def, vars, pars, name) CompositeComponentInstance(self, comps_dict, firsts, lasts, clocks) return self end # Constructs types of vars and params from sub-components - function CompositeComponentInstance(comps::Vector{AbstractComponentInstance}, - comp_def::ComponentDef, + function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractComponentDef, name::Symbol=nameof(comp_def)) - (TV, TP) = _comp_instance_types(comps) - self = new{TV, TP}() + (vars, pars) = _comp_instance_vars_pars(comps) + self = new{typeof(vars), typeof(pars)}() CompositeComponentInstance(self, comps, comp_def, name) end end @@ -488,13 +495,40 @@ end @method is_leaf(ci::CompositeComponentInstance) = false @method is_composite(ci::ComponentInstance) = !is_leaf(ci) -# TBD: write these -function _comp_instance_types(comps::Vector{AbstractComponentInstance}) - error("Need to define comp_instance_types") -end +# +# TBD: Should include only exported vars and pars, right? +# TBD: Use (from build.jl) _combine_exported_vars & _pars? +# +""" +Create a single ComponentInstanceParameters type reflecting those of a composite +component's parameters, and similarly for its variables. +""" +function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) + vtypes = [] + vnames = [] + vvalues = [] + ptypes = [] + pnames = [] + pvalues = [] + + for comp in comps + v = comp.variables + p = comp.parameters + + append!(vnames, names(v)) + append!(pnames, names(p)) + + append!(vtypes, types(v)) + append!(ptypes, types(p)) + + append!(vvalues, values(v)) + append!(pvalues, values(p)) + end + + vars = ComponentInstanceVariables(Tuple(vnames), Tuple{vtypes...}, vvalues) + pars = ComponentInstanceParameters(Tuple(pnames), Tuple{ptypes...}, pvalues) -function _collect_vars_pars(comps::Vector{AbstractComponentInstance}) - error("Need to define comp_instance_types") + return vars, pars end # A container class that wraps the dimension dictionary when passed to run_timestep() @@ -513,13 +547,11 @@ Base.getproperty(dimdict::DimDict, property::Symbol) = getfield(dimdict, :dict)[ @class ModelInstance <: CompositeComponentInstance begin md::ModelDef - # similar to generated constructor, but taking TV and TP from superclass instance - # function ModelInstance(md::ModelDef, s::CompositeComponentInstance{TV, TP}) where - # {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - # new{TV, TP}(s.comp_name, s.comp_id, s.variables, s.parameters, s.dim_dict, s.first, s.last, - # s.init, s.run_timestep, s.comps_dict, s.firsts, s.lasts, s.clocks, md) - # end + # Similar to generated constructor, but extract {TV, TP} from argument. + function ModelInstance(cci::CompositeComponentInstance{TV, TP}, md::ModelDef) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + return ModelInstance{TV, TP}(cci, md) + end end # diff --git a/test/test_composite.jl b/test/test_composite.jl index a9be8e0bc..f9b0064ef 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,8 +4,8 @@ using Test using Mimi import Mimi: - ComponentId, DatumReference, ComponentDef, BindingTypes, ModelDef, - build, time_labels, reset_compdefs, compdef + ComponentId, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, + BindingTypes, ModelDef, build, time_labels, reset_compdefs, compdef reset_compdefs() @@ -48,22 +48,23 @@ let calling_module = @__MODULE__ ccname = :testcomp ccid = ComponentId(calling_module, ccname) - comps::Vector{<: _ComponentDef_} = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] + comps = AbstractComponentDef[compdef(Comp1), compdef(Comp2), compdef(Comp3)] # TBD: need to implement this to create connections and default value bindings::Vector{Pair{DatumReference, BindingTypes}} = [ - DatumReference(Comp1, :par_1_1) => 5, # bind Comp1.par_1_1 to constant value of 5 - DatumReference(Comp2, :par_2_2) => DatumReference(Comp1, :var_1_1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - DatumReference(Comp3, :par_3_1) => DatumReference(Comp2, :var_2_1) + DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 + DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) ] exports = [ - DatumReference(Comp1, :par_1_1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - DatumReference(Comp2, :par2_2) => :c2p2, - DatumReference(Comp3, :var_3_1) => :c3v1 + DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + DatumReference(:par_2_2, Comp2) => :c2p2, + DatumReference(:var_3_1, Comp3) => :c3v1 ] - MyComposite.md = ModelDef(ComponentDef(ccid, ccname, comps, bindings, exports)) + MyComposite.md = md = ModelDef() + CompositeComponentDef(md, ccid, comps, bindings, exports) set_dimension!(MyComposite, :time, 2005:2020) nothing @@ -71,7 +72,6 @@ end m = MyComposite md = m.md -ccd = md.ccd set_param!(m, :Comp1, :par_1_1, zeros(length(time_labels(md)))) connect_param!(md, :Comp2, :par_2_1, :Comp1, :var_1_1) @@ -85,7 +85,6 @@ end # module # TBD: remove once debugged m = TestComposite.m md = m.md -ccd = md.ccd mi = m.mi nothing From d03f5d77ee8feba9b2a3976638f12d7f06fc7492 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 31 Jan 2019 14:58:32 -0800 Subject: [PATCH 27/81] WIP -- debugging composites --- src/core/build.jl | 45 ++++++------ src/core/defcomp.jl | 4 +- src/core/defs.jl | 101 +++++++++++--------------- src/core/dimensions.jl | 5 +- src/core/instances.jl | 31 ++++---- src/core/model.jl | 6 +- src/core/types.jl | 75 +++++++++++-------- test/test_composite.jl | 9 +-- test/test_variables_model_instance.jl | 6 +- wip/export_all.jl | 2 +- 10 files changed, 139 insertions(+), 145 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index e4fabc616..fb8f03fdd 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -1,7 +1,7 @@ connector_comp_name(i::Int) = Symbol("ConnectorComp$i") # Return the datatype to use for instance variables/parameters -function _instance_datatype(md::ModelDef, def::absclass(DatumDef)) +function _instance_datatype(md::ModelDef, def::AbstractDatumDef) dtype = def.datatype == Number ? number_type(md) : def.datatype dims = dim_names(def) num_dims = dim_count(def) @@ -27,7 +27,7 @@ function _instance_datatype(md::ModelDef, def::absclass(DatumDef)) end # Create the Ref or Array that will hold the value(s) for a Parameter or Variable -function _instantiate_datum(md::ModelDef, def::absclass(DatumDef)) +function _instantiate_datum(md::ModelDef, def::AbstractDatumDef) dtype = _instance_datatype(md, def) dims = dim_names(def) num_dims = length(dims) @@ -174,18 +174,6 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing end end -# Save a reference to the model's dimension dictionary to make it -# available in calls to run_timestep. -function _save_dim_dict_reference(mi::ModelInstance) - dim_dict = dim_value_dict(mi) - - for ci in components(mi) - ci.dim_dict = dim_dict - end - - return nothing -end - function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @info "Instantiating params for $(comp_def.comp_id)" @@ -199,11 +187,15 @@ function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict return ComponentInstanceParameters(pnames, ptypes, pvals) end -@method _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) = _combine_exported_pars(comp_def, par_dict) - +@method function _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) + _combine_exported_pars(comp_def, par_dict) +end # Return a built leaf or composite ComponentInstance -function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _build(comp_def::ComponentDef, + var_dict::Dict{Symbol, Any}, + par_dict::Dict{Symbol, Dict{Symbol, Any}}, + dims::DimValueDict) # FIX pass just (first, last) tuple? @info "_build leaf $(comp_def.comp_id)" @info " var_dict $(var_dict)" @info " par_dict $(par_dict)" @@ -212,18 +204,21 @@ function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::D pars = _instantiate_params(comp_def, par_dict) vars = var_dict[comp_name] - return ComponentInstance(comp_def, vars, pars, comp_name) + return ComponentInstance(comp_def, vars, pars, dims, comp_name) end -@method function _build(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +@method function _build(comp_def::CompositeComponentDef, + var_dict::Dict{Symbol, Any}, + par_dict::Dict{Symbol, Dict{Symbol, Any}}, + dims::DimValueDict) # FIX pass just (first, last) tuple? @info "_build composite $(comp_def.comp_id)" @info " var_dict $(var_dict)" @info " par_dict $(par_dict)" - comp_name = nameof(comp_def) - comps = [_build(cd, var_dict, par_dict) for cd in compdefs(comp_def)] + comps = [_build(cd, var_dict, par_dict, dims) for cd in compdefs(comp_def)] + comp_name = nameof(comp_def)å - return CompositeComponentInstance(comps, comp_def, comp_name) + return CompositeComponentInstance(comps, comp_def, dims, comp_name) end function _build(md::ModelDef) @@ -246,9 +241,11 @@ function _build(md::ModelDef) @info "var_dict: $var_dict" @info "par_dict: $par_dict" - ci = _build(md, var_dict, par_dict) + dim_val_dict = DimValueDict(dim_dict(md)) + + ci = _build(md, var_dict, par_dict, dim_val_dict) mi = ModelInstance(ci, md) - _save_dim_dict_reference(mi) + return mi end diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 1e193a6a4..4ac7364cf 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -31,7 +31,7 @@ function _generate_run_func(comp_name, args, body) func = :( global function $(func_name)($(p)::Mimi.ComponentInstanceParameters, $(v)::Mimi.ComponentInstanceVariables, - $(d)::Mimi.DimDict, + $(d)::Mimi.DimValueDict, $(t)::T) where {T <: Mimi.AbstractTimestep} $(body...) return nothing @@ -54,7 +54,7 @@ function _generate_init_func(comp_name, args, body) func = :( global function $(func_name)($(p)::Mimi.ComponentInstanceParameters, $(v)::Mimi.ComponentInstanceVariables, - $(d)::Mimi.DimDict) + $(d)::Mimi.DimValueDict) $(body...) return nothing end diff --git a/src/core/defs.jl b/src/core/defs.jl index c12e7d8a1..7ffabd1b9 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -18,7 +18,9 @@ function compdef(comp_name::Symbol) end end -# TBD: @method supports ModelDef as subclass of CompositeComponentDef, otherwise not needed +# Allows method to be called on leaf component defs, which sometimes simplifies code. +compdefs(c::ComponentDef) = [] + @method compdefs(c::CompositeComponentDef) = values(c.comps_dict) @method compkeys(c::CompositeComponentDef) = keys(c.comps_dict) @method hascomp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) @@ -44,31 +46,21 @@ first_period(comp_def::ComponentDef) = comp_def.first last_period(comp_def::ComponentDef) = comp_def.last function first_period(comp_def::CompositeComponentDef) - if numcomponents(comp_def) > 0 - firsts = [first_period(c) for c in comp_def] - if findfirst(isequal(nothing), firsts) == nothing # i.e., there are no `nothing`s - return min(Vector{Int}(firsts)...) - end - end - nothing # use model's first period + values = filter(!isnothing, [first_period(c) for c in comp_def]) + return length(values) > 0 ? min(values...) : nothing end function last_period(comp_def::CompositeComponentDef) - if numcomponents(comp_def) > 0 - lasts = [last_period(c) for c in comp_def] - if findfirst(isequal(nothing), lasts) == nothing # i.e., there are no `nothing`s - return max(Vector{Int}(lasts)...) - end - end - nothing # use model's last period + values = filter(!isnothing, [last_period(c) for c in comp_def]) + return length(values) > 0 ? max(values...) : nothing end -function first_period(md::ModelDef, comp_def::absclass(ComponentDef)) +function first_period(md::ModelDef, comp_def::AbstractComponentDef) period = first_period(comp_def) return period === nothing ? time_labels(md)[1] : period end -function last_period(md::ModelDef, comp_def::absclass(ComponentDef)) +function last_period(md::ModelDef, comp_def::AbstractComponentDef) period = last_period(comp_def) return period === nothing ? time_labels(md)[end] : period end @@ -156,9 +148,6 @@ end add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -# Allow our usual abbreviation -add_dim! = add_dimension! - @method function dim_names(ccd::CompositeComponentDef) dims = OrderedSet{Symbol}() # use a set to eliminate duplicates for cd in compdefs(ccd) @@ -242,49 +231,53 @@ end dim_names(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] - dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dim_dict(md)]) dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(md, dims)] dim_count(md::ModelDef, name::Symbol) = length(dimension(md, name)) -dim_key_dict(md::ModelDef) = Dict([name => collect(keys(dim)) for (name, dim) in dim_dict(md)]) -dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) - +dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) dim_values(md::ModelDef, name::Symbol) = collect(values(dimension(md, name))) -dim_value_dict(md::ModelDef) = Dict([name => collect(values(dim)) for (name, dim) in dim_dict(md)]) +# For debugging only +@method function _show_run_period(obj::ComponentDef, first, last) + first = (first === nothing ? :nothing : first) + last = (last === nothing ? :nothing : last) + which = (is_leaf(obj) ? :leaf : :composite) + @info "Setting run period for $which $(nameof(obj)) to ($first, $last)" +end -# Helper function invoked when the user resets the time dimension with set_dimension! -# This function calls set_run_period! on each component definition to reset the first and last values. -@method function _reset_run_periods!(ccd::CompositeComponentDef, first, last) - for comp_def in compdefs(ccd) - changed = false - first_per = first_period(comp_def) - last_per = last_period(comp_def) +""" + set_run_period!(obj::ComponentDef, first, last) - if first_per !== nothing && first_per < first - @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" - changed = true - else - first = first_per - end - - if last_per !== nothing && last_per > last - @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" - changed = true - else - last = last_per - end +Allows user to narrow the bounds on the time dimension. - if changed - set_run_period!(comp_def, first, last) - end +If the component has an earlier start than `first` or a later finish than `last`, +the values are reset to the tighter bounds. Values of `nothing` are left unchanged. +Composites recurse on sub-components. +""" +@method function set_run_period!(obj::ComponentDef, first, last) + #_show_run_period(obj, first, last) + + if first_per !== nothing && first_per < first + @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" + obj.first = first + end + + if last_per !== nothing && last_per > last + @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" + obj.last = last end + + # N.B. compdefs() returns an empty list for leaf ComponentDefs + for subcomp in compdefs(obj) + set_run_period!(subcomp, first, last) + end + nothing end """ - set_dimension!(md::ModelDef, name::Symbol, keys::Union{Int, Vector, Tuple, Range}) + set_dimension!(md::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) Set the values of `md` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. @@ -297,8 +290,8 @@ an integer; or to the values in the vector or range if `keys` is either of those if name == :time set_uniform!(ccd, isuniform(keys)) - if redefined - _reset_run_periods!(ccd, keys[1], keys[end]) + if redefined + set_run_period!(ccd, keys[1], keys[end]) end end @@ -589,12 +582,6 @@ end return size(times[first_index:last_index]) end -@method function set_run_period!(comp_def::ComponentDef, first, last) - comp_def.first = first - comp_def.last = last - return nothing -end - # # Model # diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index 7d80abb18..057404eb3 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -44,8 +44,9 @@ Base.iterate(dim::RangeDimension, state...) = iterate(dim.range, state...) Base.keys(dim::RangeDimension) = collect(dim.range) Base.values(dim::RangeDimension) = collect(1:length(dim.range)) -# Get last value from OrderedDict of keys -Base.lastindex(dim::AbstractDimension) = dim.dict.keys[length(dim)] +# Get first/last value from OrderedDict of keys +Base.firstindex(dim::AbstractDimension) = dim.dict.keys[1] +Base.lastindex(dim::AbstractDimension) = dim.dict.keys[length(dim)] # # Compute the index of a "key" (e.g., a year) in the range. diff --git a/src/core/instances.jl b/src/core/instances.jl index d2c664638..0b57fa2fc 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -10,15 +10,15 @@ Return the `ModelDef` contained by ModelInstance `mi`. modeldef(mi::ModelInstance) = mi.md """ - @method add_comp!(obj::CompositeComponentInstance, ci::absclass(ComponentInstance)) + @method add_comp!(obj::CompositeComponentInstance, ci::AbstractComponentInstance) Add the (leaf or composite) component `ci` to a composite's list of components, and add the `first` and `last` of `mi` to the ends of the composite's `firsts` and `lasts` lists. """ -@method function add_comp!(obj::CompositeComponentInstance, ci::absclass(ComponentInstance)) +@method function add_comp!(obj::CompositeComponentInstance, ci::AbstractComponentInstance) obj.comps_dict[nameof(ci)] = ci - push!(obj.firsts, first_period(ci)) + push!(obj.firsts, first_period(ci)) # TBD: perhaps this should be set when time is set? push!(obj.lasts, last_period(ci)) nothing end @@ -164,10 +164,6 @@ Return the size of index `dim_name`` in model instance `mi`. """ @delegate dim_count(mi::ModelInstance, dim_name::Symbol) => md -@delegate dim_key_dict(mi::ModelInstance) => md - -@delegate dim_value_dict(mi::ModelInstance) => md - # TBD: make this a @method? function make_clock(mi::ModelInstance, ntimesteps, time_keys::Vector{Int}) last = time_keys[min(length(time_keys), ntimesteps)] @@ -209,11 +205,11 @@ end return nothing end -function init(ci::ComponentInstance) +@method function init(ci::ComponentInstance) reset_variables(ci) if ci.init != nothing - ci.init(parameters(ci), variables(ci), dims(ci)) + ci.init(parameters(ci), variables(ci), dim_value_dict(ci)) end return nothing end @@ -225,9 +221,9 @@ end return nothing end -function run_timestep(ci::ComponentInstance, clock::Clock) +@method function run_timestep(ci::ComponentInstance, clock::Clock) if ci.run_timestep != nothing - ci.run_timestep(parameters(ci), variables(ci), dims(ci), clock.ts) + ci.run_timestep(parameters(ci), variables(ci), dim_value_dict(ci), clock.ts) end # TBD: move this outside this func if components share a clock @@ -263,16 +259,22 @@ function _run_components(mi::ModelInstance, clock::Clock, nothing end +# TBD: some of this (e.g., firsts/lasts) should be computed at each recursive level. +# TBD: We have firsts/lasts in each (Composite)ComponentInstance. Compute this at build time. + +# TBD: Write (or find) a reset(clock::Clock) method + function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dimkeys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) if length(components(mi)) == 0 error("Cannot run the model: no components have been created.") end - t::Vector{Int} = dimkeys === nothing ? dim_keys(mi, :time) : dimkeys[:time] + md = mi.md + t::Vector{Int} = dimkeys === nothing ? dim_keys(md, :time) : dimkeys[:time] - firsts_vec = firsts(mi) - lasts_vec = lasts(mi) + firsts_vec = Vector{Int}(mi.firsts) # build step replaces `nothing` values with Ints + lasts_vec = Vector{Int}(mi.lasts) if isuniform(t) stepsize = step_size(t) @@ -291,6 +293,7 @@ function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), init(mi) # call module's (or fallback) init function + @info "run: firsts: $firsts_vec, lasts: $lasts_vec" _run_components(mi, clock, firsts_vec, lasts_vec, comp_clocks) nothing end diff --git a/src/core/model.jl b/src/core/model.jl index 19bb96a7f..8940b3800 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -19,10 +19,6 @@ is_built(m::Model) = (modelinstance(m) !== nothing) @delegate compinstance(m::Model, name::Symbol) => mi @delegate has_comp(m::Model, name::Symbol) => mi -@delegate firsts(m::Model) => mi -@delegate lasts(m::Model) => mi -@delegate clocks(m::Model) => mi - @delegate number_type(m::Model) => md @delegate internal_param_conns(m::Model) => md @@ -214,6 +210,8 @@ an integer; or to the values in the vector or range if `keys`` is either of thos """ @delegate(set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => md, decache(m)) +@delegate(set_run_period!(m::Model, first, last) => md, decache(m)) + @delegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md @delegate parameter_names(m::Model, comp_name::Symbol) => md diff --git a/src/core/types.jl b/src/core/types.jl index 0fad1e858..f826e66d6 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -374,12 +374,28 @@ function ComponentInstanceVariables(names, types, values) return ComponentInstanceVariables{NT}(NT(values)) end +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimValueDict + dict::Dict{Symbol, Vector{Int}} + + function DimValueDict(dim_dict::Dict{Symbol, Vector{<: AbstractDimension}}) + d = Dict([name => collect(values(dim)) for (name, dim) in dim_dict]) + new(d) + end +end + +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimValueDict of dimensions by name +# as the "d" parameter. +Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] + @class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin comp_name::Symbol comp_id::ComponentId variables::TV parameters::TP - dim_dict::Dict{Symbol, Vector{Int}} + dim_value_dict::DimValueDict first::Union{Nothing, Int} last::Union{Nothing, Int} init::Union{Nothing, Function} @@ -387,17 +403,22 @@ end function ComponentInstance(self::AbstractComponentInstance, comp_def::AbstractComponentDef, - vars::TV, pars::TP, + vars::TV, pars::TP, dims::DimValueDict, + time::AbstractDimension, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self.comp_id = comp_id = comp_def.comp_id self.comp_name = name - self.dim_dict = Dict{Symbol, Vector{Int}}() # values set in "build" stage + self.dim_value_dict = dims self.variables = vars self.parameters = pars - self.first = comp_def.first - self.last = comp_def.last + + # If first or last is `nothing`, substitute first or last time period + t = dims.time + choose(a, b) = (a !== nothing ? a : b) + self.first = choose(comp_def.first, t[1]) + self.last = choose(comp_def.last, t[end]) @info "ComponentInstance evaluating $(comp_id.module_name)" comp_module = Main.eval(comp_id.module_name) @@ -432,19 +453,19 @@ end end end -@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, +@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, dims::DimValueDict, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = ComponentInstance{TV, TP}() - return ComponentInstance(self, comp_def, vars, pars, name) + return ComponentInstance(self, comp_def, vars, pars, dims, name) end # These can be called on CompositeComponentInstances and ModelInstances @method compdef(obj::ComponentInstance) = compdef(comp_id(obj)) -@method dims(obj::ComponentInstance) = obj.dim_dict -@method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_dict, name) -@method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_dict[name] +@method dim_value_dict(obj::ComponentInstance) = obj.dim_value_dict +@method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) +@method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_value_dict[name] @method first_period(obj::ComponentInstance) = obj.first @method mutable(obj::ComponentInstance) = obj.last @@ -457,21 +478,24 @@ end function CompositeComponentInstance(self::AbstractCompositeComponentInstance, comps::Vector{<: AbstractComponentInstance}, comp_def::AbstractComponentDef, + dims::DimValueDict, name::Symbol=nameof(comp_def)) comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() - firsts = Vector{Union{Nothing, Int}}() - lasts = Vector{Union{Nothing, Int}}() - clocks = Vector{Clock}() - for ci in comps + # pre-allocate these since we know the length + count = length(comps) + firsts = Vector{Union{Nothing, Int}}(undef, count) + lasts = Vector{Union{Nothing, Int}}(undef, count) + clocks = Vector{Clock}(undef, count) + + for (i, ci) in enumerate(comps) comps_dict[ci.comp_name] = ci - push!(firsts, ci.first) - push!(lasts, ci.last) - # push!(clocks, ?) + firsts[i] = ci.first + lasts[i] = ci.last end (vars, pars) = _comp_instance_vars_pars(comps) - ComponentInstance(self, comp_def, vars, pars, name) + ComponentInstance(self, comp_def, vars, pars, dims, name) CompositeComponentInstance(self, comps_dict, firsts, lasts, clocks) return self end @@ -479,10 +503,11 @@ end # Constructs types of vars and params from sub-components function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, comp_def::AbstractComponentDef, + dims::DimValueDict, name::Symbol=nameof(comp_def)) (vars, pars) = _comp_instance_vars_pars(comps) self = new{typeof(vars), typeof(pars)}() - CompositeComponentInstance(self, comps, comp_def, name) + CompositeComponentInstance(self, comps, comp_def, dims, name) end end @@ -531,18 +556,6 @@ function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) return vars, pars end -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimDict - dict::Dict{Symbol, Vector{Int}} -end - -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimDict of dimensions by name -# as the "d" parameter. -Base.getproperty(dimdict::DimDict, property::Symbol) = getfield(dimdict, :dict)[property] - - # ModelInstance holds the built model that is ready to be run @class ModelInstance <: CompositeComponentInstance begin md::ModelDef diff --git a/test/test_composite.jl b/test/test_composite.jl index f9b0064ef..d5391ea81 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -44,7 +44,7 @@ end # Test the calls the macro will produce let calling_module = @__MODULE__ # calling_module = TestComposite - global MyComposite = Model() + global m = Model() ccname = :testcomp ccid = ComponentId(calling_module, ccname) @@ -63,14 +63,13 @@ let calling_module = @__MODULE__ DatumReference(:var_3_1, Comp3) => :c3v1 ] - MyComposite.md = md = ModelDef() + m.md = md = ModelDef() CompositeComponentDef(md, ccid, comps, bindings, exports) - set_dimension!(MyComposite, :time, 2005:2020) + set_dimension!(m, :time, 2005:2020) nothing end -m = MyComposite md = m.md set_param!(m, :Comp1, :par_1_1, zeros(length(time_labels(md)))) @@ -78,7 +77,7 @@ connect_param!(md, :Comp2, :par_2_1, :Comp1, :var_1_1) connect_param!(md, :Comp2, :par_2_2, :Comp1, :var_1_1) connect_param!(md, :Comp3, :par_3_1, :Comp2, :var_2_1) -build(MyComposite) +build(m) end # module diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index efc020759..e8f5ca820 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -5,7 +5,7 @@ using Test import Mimi: reset_compdefs, variable_names, compinstance, get_var_value, get_param_value, - set_param_value, set_var_value, dim_count, dim_key_dict, dim_value_dict, compdef, + set_param_value, set_var_value, dim_count, compdef, ComponentInstance, ComponentDef, TimestepArray, ComponentInstanceParameters, ComponentInstanceVariables @@ -68,9 +68,5 @@ vars2 = variables(ci) @test vars == vars2 @test dim_count(mi, :time) == 20 -key_dict = dim_key_dict(mi) -value_dict = dim_value_dict(mi) -@test [key_dict[:time]...] == [2015:5:2110...] && length(key_dict) == 1 -@test value_dict[:time] == [1:1:20...] && length(value_dict) == 1 end #module diff --git a/wip/export_all.jl b/wip/export_all.jl index 75bd1b63e..f3541d046 100644 --- a/wip/export_all.jl +++ b/wip/export_all.jl @@ -3,7 +3,7 @@ # macro import_all(pkg) function ok_to_import(symbol) - ! (symbol in (:eval, :show, :include) || string(symbol)[1] == '#') + ! (symbol in (:eval, :show, :include, :name) || string(symbol)[1] == '#') end symbols = Iterators.filter(ok_to_import, names(eval(pkg), all=true)) From c475db3c8114923827a610393717279b8993b8bc Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 1 Feb 2019 13:04:01 -0800 Subject: [PATCH 28/81] WIP commit --- src/core/build.jl | 43 ++++++------ src/core/defcomp.jl | 5 +- src/core/defs.jl | 2 + src/core/instances.jl | 156 ++++++++++++++++++++++------------------- src/core/time.jl | 24 ++++++- src/core/types.jl | 59 ++++++++-------- test/test_composite.jl | 1 + 7 files changed, 162 insertions(+), 128 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index fb8f03fdd..b049d1d94 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -113,7 +113,7 @@ function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{ par_dict[comp_name] = Dict() var_dict[comp_name] = v = _instantiate_component_vars(md, comp_def) - @info "_instantiate_vars leaf $comp_name: $v" + # @info "_instantiate_vars leaf $comp_name: $v" end # Creates the top-level vars for the model @@ -127,12 +127,12 @@ end comp_name = nameof(comp_def) par_dict[comp_name] = Dict() - @info "_instantiate_vars composite $comp_name" + # @info "_instantiate_vars composite $comp_name" for cd in compdefs(comp_def) _instantiate_vars(cd, md, var_dict, par_dict) end var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) - @info "composite vars for $comp_name: $v " + # @info "composite vars for $comp_name: $v " end # Do nothing if called on a leaf component @@ -145,7 +145,7 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing _collect_params(cd, var_dict, par_dict) end - @info "Collecting params for $(comp_def.comp_id)" + # @info "Collecting params for $(comp_def.comp_id)" # Iterate over connections to create parameters, referencing storage in vars for ipc in internal_param_conns(comp_def) @@ -175,7 +175,7 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing end function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - @info "Instantiating params for $(comp_def.comp_id)" + # @info "Instantiating params for $(comp_def.comp_id)" comp_name = nameof(comp_def) d = par_dict[comp_name] @@ -195,30 +195,28 @@ end function _build(comp_def::ComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}, - dims::DimValueDict) # FIX pass just (first, last) tuple? - @info "_build leaf $(comp_def.comp_id)" - @info " var_dict $(var_dict)" - @info " par_dict $(par_dict)" + time_bounds::Tuple{Int, Int}) + # @info "_build leaf $(comp_def.comp_id)" + # @info " var_dict $(var_dict)" + # @info " par_dict $(par_dict)" comp_name = nameof(comp_def) pars = _instantiate_params(comp_def, par_dict) vars = var_dict[comp_name] - return ComponentInstance(comp_def, vars, pars, dims, comp_name) + return ComponentInstance(comp_def, vars, pars, time_bounds) end @method function _build(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}, - dims::DimValueDict) # FIX pass just (first, last) tuple? - @info "_build composite $(comp_def.comp_id)" - @info " var_dict $(var_dict)" - @info " par_dict $(par_dict)" + time_bounds::Tuple{Int, Int}) + # @info "_build composite $(comp_def.comp_id)" + # @info " var_dict $(var_dict)" + # @info " par_dict $(par_dict)" - comps = [_build(cd, var_dict, par_dict, dims) for cd in compdefs(comp_def)] - comp_name = nameof(comp_def)å - - return CompositeComponentInstance(comps, comp_def, dims, comp_name) + comps = [_build(cd, var_dict, par_dict, time_bounds) for cd in compdefs(comp_def)] + return CompositeComponentInstance(comps, comp_def, time_bounds) end function _build(md::ModelDef) @@ -238,12 +236,13 @@ function _build(md::ModelDef) _instantiate_vars(md, var_dict, par_dict) _collect_params(md, var_dict, par_dict) - @info "var_dict: $var_dict" - @info "par_dict: $par_dict" + # @info "var_dict: $var_dict" + # @info "par_dict: $par_dict" - dim_val_dict = DimValueDict(dim_dict(md)) + t = dimension(md, :time) + time_bounds = (firstindex(t), lastindex(t)) - ci = _build(md, var_dict, par_dict, dim_val_dict) + ci = _build(md, var_dict, par_dict, time_bounds) mi = ModelInstance(ci, md) return mi diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 4ac7364cf..f061122aa 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -283,11 +283,12 @@ macro defmodel(model_name, ex) for elt in elements offset = 0 - if @capture(elt, component(comp_mod_name_name_.comp_name_) | component(comp_name_) | + if @capture(elt, component(comp_mod_name_.comp_name_) | component(comp_name_) | component(comp_mod_name_.comp_name_, alias_) | component(comp_name_, alias_)) # set local copy of comp_mod_name to the stated or default component module - expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) + expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) + : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) addexpr(expr) name = (alias === nothing ? comp_name : alias) diff --git a/src/core/defs.jl b/src/core/defs.jl index 7ffabd1b9..ca4384fea 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -257,6 +257,8 @@ Composites recurse on sub-components. """ @method function set_run_period!(obj::ComponentDef, first, last) #_show_run_period(obj, first, last) + first_per = first_period(obj) + last_per = last_period(obj) if first_per !== nothing && first_per < first @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" diff --git a/src/core/instances.jl b/src/core/instances.jl index 0b57fa2fc..5f82305c4 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -18,8 +18,8 @@ the `first` and `last` of `mi` to the ends of the composite's `firsts` and `last @method function add_comp!(obj::CompositeComponentInstance, ci::AbstractComponentInstance) obj.comps_dict[nameof(ci)] = ci - push!(obj.firsts, first_period(ci)) # TBD: perhaps this should be set when time is set? - push!(obj.lasts, last_period(ci)) + # push!(obj.firsts, first_period(ci)) # TBD: perhaps this should be set when time is set? + # push!(obj.lasts, last_period(ci)) nothing end @@ -164,21 +164,6 @@ Return the size of index `dim_name`` in model instance `mi`. """ @delegate dim_count(mi::ModelInstance, dim_name::Symbol) => md -# TBD: make this a @method? -function make_clock(mi::ModelInstance, ntimesteps, time_keys::Vector{Int}) - last = time_keys[min(length(time_keys), ntimesteps)] - - if isuniform(time_keys) - first, stepsize = first_and_step(time_keys) - return Clock{FixedTimestep}(first, stepsize, last) - - else - last_index = findfirst(isequal(last), time_keys) - times = (time_keys[1:last_index]...,) - return Clock{VariableTimestep}(times) - end -end - @method function reset_variables(ci::ComponentInstance) # println("reset_variables($(ci.comp_id))") vars = ci.variables @@ -209,91 +194,120 @@ end reset_variables(ci) if ci.init != nothing - ci.init(parameters(ci), variables(ci), dim_value_dict(ci)) + ci.init(ci.parameters, ci.variables, dims) end return nothing end -@method function init(obj::CompositeComponentInstance) +@method function init(obj::CompositeComponentInstance, dims::DimValueDict) for ci in components(obj) - init(ci) + init(ci, dims) end return nothing end -@method function run_timestep(ci::ComponentInstance, clock::Clock) - if ci.run_timestep != nothing - ci.run_timestep(parameters(ci), variables(ci), dim_value_dict(ci), clock.ts) - end +# @method function run_timestep(ci::ComponentInstance, clock::Clock) +# if ci.run_timestep !== nothing +# ci.run_timestep(parameters(ci), variables(ci), dim_value_dict(ci), clock.ts) +# end - # TBD: move this outside this func if components share a clock - advance(clock) +# # TBD: move this outside this func if components share a clock +# advance(clock) - return nothing -end +# return nothing +# end -@method function run_timestep(obj::CompositeComponentInstance, clock::Clock) - for ci in components(obj) - run_timestep(ci, clock) +# @method function run_timestep(obj::CompositeComponentInstance, clock::Clock) +# for ci in components(obj) +# run_timestep(ci, clock) +# end +# return nothing +# end + +@method _runnable(ci::ComponentInstance, clock::Clock) = (ci.first <= gettime(clock) <= ci.last) + +# +# New versions +# +@method function run_timestep(ci::ComponentInstance, clock::Clock, dims::DimValueDict) + if ci.run_timestep !== nothing && _runnable(ci, clock) + ci.run_timestep(ci.parameters, ci.variables, dims, clock.ts) end + return nothing end -function _run_components(mi::ModelInstance, clock::Clock, - firsts::Vector{Int}, lasts::Vector{Int}, - comp_clocks::Vector{Clock{T}}) where {T <: AbstractTimestep} - @info "_run_components: firsts: $firsts, lasts: $lasts" - comp_instances = components(mi) - - # collect these since we iterate over them repeatedly below - tups = collect(zip(comp_instances, firsts, lasts, comp_clocks)) - - while ! finished(clock) - for (ci, first, last, comp_clock) in tups - if first <= gettime(clock) <= last - run_timestep(ci, comp_clock) - end +@method function run_timestep(cci::CompositeComponentInstance, clock::Clock, dims::DimValueDict) + if _runnable(cci, clock) + for ci in components(cci) + run_timestep(ci, clock, dims) end - advance(clock) end - nothing + return nothing end -# TBD: some of this (e.g., firsts/lasts) should be computed at each recursive level. -# TBD: We have firsts/lasts in each (Composite)ComponentInstance. Compute this at build time. +# +# TBD: might be obsolete +# +""" + function _make_clocks(ci::AbstractComponentInstance, time_keys::Vector{Int}) -# TBD: Write (or find) a reset(clock::Clock) method +Store a vector of of Clocks into a composite instance's `clocks` member, +and repeat recursively through any subcomps. For non-composites, do nothing. +""" +# _make_clocks(ci::ComponentInstance, time_keys::Vector{Int}) = nothing + +# @method function _make_clocks(ci::CompositeComponentInstance, time_keys::Vector{Int}) +# clocks = ci.clocks # preallocated in constructor + +# if isuniform(time_keys) +# stepsize = step_size(time_keys) +# for (i, (first, last)) in enumerate(zip(ci.firsts, ci.lasts)) +# clocks[i] = Clock{FixedTimestep}(first, stepsize, last) +# end +# else +# for (i, (first, last)) in enumerate(zip(ci.firsts, ci.lasts)) +# first_index = findfirst(isequal(first), time_keys) +# last_index = findfirst(isequal(last), time_keys) +# times = Tuple(time_keys[first_index:last_index]) +# clocks[i] = Clock{VariableTimestep}(times) +# end +# end + +# for subcomp in components(ci) +# _make_clocks(subcomp, time_keys) +# end +# end + +# TBD: Write a reset(clock::Clock) method? function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dimkeys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) - if length(components(mi)) == 0 + + if (ncomps = length(components(mi))) == 0 error("Cannot run the model: no components have been created.") end - md = mi.md - t::Vector{Int} = dimkeys === nothing ? dim_keys(md, :time) : dimkeys[:time] - - firsts_vec = Vector{Int}(mi.firsts) # build step replaces `nothing` values with Ints - lasts_vec = Vector{Int}(mi.lasts) + time_keys::Vector{Int} = dimkeys === nothing ? dim_keys(mi.md, :time) : dimkeys[:time] - if isuniform(t) - stepsize = step_size(t) - comp_clocks = [Clock{FixedTimestep}(first, stepsize, last) for (first, last) in zip(firsts_vec, lasts_vec)] - else - comp_clocks = Array{Clock{VariableTimestep}}(undef, length(firsts_vec)) - for i = 1:length(firsts_vec) - first_index = findfirst(isequal(firsts_vec[i]), t) - last_index = findfirst(isequal(lasts_vec[i]), t) - times = (t[first_index:last_index]...,) - comp_clocks[i] = Clock{VariableTimestep}(times) - end + # truncate time_keys if caller so desires + if ntimesteps < length(time_keys) + time_keys = time_keys[1:ntimesteps] end - clock = make_clock(mi, ntimesteps, t) + # _make_clocks(mi, time_keys) # pre-generate all required sub-component clocks + + # TBD: Pass this, but substitute t from above? + dim_val_dict = DimValueDict(dim_dict(mi.md)) - init(mi) # call module's (or fallback) init function + # recursively initializes all components + init(mi) + + clock = Clock(time_keys) + while ! finished(clock) + run_timestep(mi, clock, dim_val_dict) + advance(clock) + end - @info "run: firsts: $firsts_vec, lasts: $lasts_vec" - _run_components(mi, clock, firsts_vec, lasts_vec, comp_clocks) nothing end diff --git a/src/core/time.jl b/src/core/time.jl index ea57c537d..67f19240f 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -76,7 +76,7 @@ end function next_timestep(ts::FixedTimestep{FIRST, STEP, LAST}) where {FIRST, STEP, LAST} if finished(ts) - error("Cannot get next timestep, this is last timestep.") + error("Cannot get next timestep, this is last timestep.") end return FixedTimestep{FIRST, STEP, LAST}(ts.t + 1) end @@ -89,7 +89,7 @@ function next_timestep(ts::VariableTimestep{TIMES}) where {TIMES} end function Base.:-(ts::FixedTimestep{FIRST, STEP, LAST}, val::Int) where {FIRST, STEP, LAST} - if is_first(ts) + if val != 0 && is_first(ts) error("Cannot get previous timestep, this is first timestep.") elseif ts.t - val <= 0 error("Cannot get requested timestep, precedes first timestep.") @@ -98,7 +98,7 @@ function Base.:-(ts::FixedTimestep{FIRST, STEP, LAST}, val::Int) where {FIRST, S end function Base.:-(ts::VariableTimestep{TIMES}, val::Int) where {TIMES} - if is_first(ts) + if val != 0 && is_first(ts) error("Cannot get previous timestep, this is first timestep.") elseif ts.t - val <= 0 error("Cannot get requested timestep, precedes first timestep.") @@ -129,6 +129,19 @@ end # 2. CLOCK # +function Clock(time_keys::Vector{Int}) + last = time_keys[end] + + if isuniform(time_keys) + first, stepsize = first_and_step(time_keys) + return Clock{FixedTimestep}(first, stepsize, last) + else + last_index = findfirst(isequal(last), time_keys) + times = (time_keys[1:last_index]...,) + return Clock{VariableTimestep}(times) + end +end + function timestep(c::Clock) return c.ts end @@ -155,6 +168,11 @@ function finished(c::Clock) return finished(c.ts) end +function reset(c::Clock) + c.ts = c.ts - (c.ts.t - 1) + nothing +end + # # 3. TimestepVector and TimestepMatrix # diff --git a/src/core/types.jl b/src/core/types.jl index f826e66d6..86e00215a 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -315,10 +315,12 @@ end return CompositeComponentDef(self, comp_id, comps, bindings, exports) end + # Creates an empty composite compdef with all containers allocated but empty function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) self = (self === nothing ? new() : self) - comp_id = ComponentId(:anonymous, :anonymous) # TBD: pass these in? + anon = gensym(:anonymous) + comp_id = ComponentId(anon, anon) comps = Vector{T where T <: AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() @@ -379,7 +381,7 @@ end struct DimValueDict dict::Dict{Symbol, Vector{Int}} - function DimValueDict(dim_dict::Dict{Symbol, Vector{<: AbstractDimension}}) + function DimValueDict(dim_dict::AbstractDict) d = Dict([name => collect(values(dim)) for (name, dim) in dim_dict]) new(d) end @@ -395,7 +397,6 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro comp_id::ComponentId variables::TV parameters::TP - dim_value_dict::DimValueDict first::Union{Nothing, Int} last::Union{Nothing, Int} init::Union{Nothing, Function} @@ -403,24 +404,21 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro function ComponentInstance(self::AbstractComponentInstance, comp_def::AbstractComponentDef, - vars::TV, pars::TP, dims::DimValueDict, - time::AbstractDimension, + vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self.comp_id = comp_id = comp_def.comp_id self.comp_name = name - self.dim_value_dict = dims self.variables = vars self.parameters = pars # If first or last is `nothing`, substitute first or last time period - t = dims.time - choose(a, b) = (a !== nothing ? a : b) - self.first = choose(comp_def.first, t[1]) - self.last = choose(comp_def.last, t[end]) + self.first = comp_def.first !== nothing ? comp_def.first : time_bounds[1] + self.last = comp_def.last !== nothing ? comp_def.last : time_bounds[2] - @info "ComponentInstance evaluating $(comp_id.module_name)" + # @info "ComponentInstance evaluating $(comp_id.module_name)" comp_module = Main.eval(comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) @@ -453,61 +451,62 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro end end -@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, dims::DimValueDict, +@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self = ComponentInstance{TV, TP}() - return ComponentInstance(self, comp_def, vars, pars, dims, name) + return ComponentInstance(self, comp_def, vars, pars, time_bounds, name) end # These can be called on CompositeComponentInstances and ModelInstances @method compdef(obj::ComponentInstance) = compdef(comp_id(obj)) -@method dim_value_dict(obj::ComponentInstance) = obj.dim_value_dict +# @method dim_value_dict(obj::ComponentInstance) = obj.dim_value_dict @method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) @method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_value_dict[name] @method first_period(obj::ComponentInstance) = obj.first -@method mutable(obj::ComponentInstance) = obj.last +@method last_period(obj::ComponentInstance) = obj.last @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} - firsts::Vector{Union{Nothing, Int}} # in order corresponding with components - lasts::Vector{Union{Nothing, Int}} - clocks::Vector{Clock} + # firsts::Vector{Int} # in order corresponding with components + # lasts::Vector{Int} + # clocks::Vector{Clock} function CompositeComponentInstance(self::AbstractCompositeComponentInstance, comps::Vector{<: AbstractComponentInstance}, - comp_def::AbstractComponentDef, - dims::DimValueDict, + comp_def::AbstractComponentDef, + time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() # pre-allocate these since we know the length - count = length(comps) - firsts = Vector{Union{Nothing, Int}}(undef, count) - lasts = Vector{Union{Nothing, Int}}(undef, count) - clocks = Vector{Clock}(undef, count) + # count = length(comps) + # firsts = Vector{Int}(undef, count) + # lasts = Vector{Int}(undef, count) + # clocks = Vector{Clock}(undef, count) for (i, ci) in enumerate(comps) comps_dict[ci.comp_name] = ci - firsts[i] = ci.first - lasts[i] = ci.last + # firsts[i] = ci.first + # lasts[i] = ci.last end (vars, pars) = _comp_instance_vars_pars(comps) - ComponentInstance(self, comp_def, vars, pars, dims, name) - CompositeComponentInstance(self, comps_dict, firsts, lasts, clocks) + ComponentInstance(self, comp_def, vars, pars, time_bounds, name) + CompositeComponentInstance(self, comps_dict) #, time_bounds, clocks) return self end # Constructs types of vars and params from sub-components function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, comp_def::AbstractComponentDef, - dims::DimValueDict, + time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) (vars, pars) = _comp_instance_vars_pars(comps) self = new{typeof(vars), typeof(pars)}() - CompositeComponentInstance(self, comps, comp_def, dims, name) + CompositeComponentInstance(self, comps, comp_def, time_bounds, name) end end diff --git a/test/test_composite.jl b/test/test_composite.jl index d5391ea81..cf9e4b970 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -78,6 +78,7 @@ connect_param!(md, :Comp2, :par_2_2, :Comp1, :var_1_1) connect_param!(md, :Comp3, :par_3_1, :Comp2, :var_2_1) build(m) +run(m) end # module From 4a89d0ed103ea275ad89b7b6c93bef286d425278 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 1 Feb 2019 14:16:16 -0800 Subject: [PATCH 29/81] WIP - Passing 436 tests... --- src/core/build.jl | 6 +- src/core/connections.jl | 2 +- src/core/defcomp.jl | 2 +- src/core/defs.jl | 18 ++--- src/core/delegate.jl | 79 ------------------- src/core/instances.jl | 63 +-------------- src/core/types.jl | 20 +---- test/test_composite.jl | 6 +- test/test_dependencies.jl | 2 +- test/test_main_variabletimestep.jl | 2 +- test/test_metainfo.jl | 2 +- test/test_metainfo_variabletimestep.jl | 2 +- test/test_model_structure.jl | 6 +- test/test_model_structure_variabletimestep.jl | 2 +- test/test_replace_comp.jl | 4 +- 15 files changed, 34 insertions(+), 182 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index b049d1d94..6c0ce3dd1 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -153,14 +153,14 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing var_value_obj = get_property_obj(src_vars, ipc.src_var_name) comp_pars = par_dict[ipc.dst_comp_name] comp_pars[ipc.dst_par_name] = var_value_obj - @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" + # @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" end for ext in external_param_conns(comp_def) param = external_param(comp_def, ext.external_param) comp_pars = par_dict[ext.comp_name] comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) - @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" + # @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" end # Make the external parameter connections for the hidden ConnectorComps. @@ -170,7 +170,7 @@ _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing param = external_param(comp_def, backup) comp_pars = par_dict[conn_comp_name] comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) - @info "backup: $conn_comp_name $param" + # @info "backup: $conn_comp_name $param" end end diff --git a/src/core/connections.jl b/src/core/connections.jl index f863d4a56..92dcdddb0 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -329,8 +329,8 @@ end Add an array type parameter `name` with value `value` and `dims` dimensions to the model 'm'. """ function set_external_array_param!(md::ModelDef, name::Symbol, value::AbstractArray, dims) + numtype = number_type(md) if !(typeof(value) <: Array{numtype}) - numtype = number_type(md) # Need to force a conversion (simple convert may alias in v0.6) value = Array{numtype}(undef, value) end diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index f061122aa..c7ff20911 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -314,7 +314,7 @@ macro defmodel(model_name, ex) else # Pass through anything else to allow the user to define intermediate vars, etc. - println("Passing through: $elt") + @info "Passing through: $elt" expr = elt end diff --git a/src/core/defs.jl b/src/core/defs.jl index ca4384fea..260722934 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -119,9 +119,7 @@ end Delete a `component` by name from a model definition `m`. """ -@delegate Base.delete!(md::ModelDef, comp_name::Symbol) => ccd - -function Base.delete!(ccd::CompositeComponentDef, comp_name::Symbol) +@method function Base.delete!(ccd::CompositeComponentDef, comp_name::Symbol) if ! hascomp(ccd, comp_name) error("Cannot delete '$comp_name': component does not exist.") end @@ -292,9 +290,7 @@ an integer; or to the values in the vector or range if `keys` is either of those if name == :time set_uniform!(ccd, isuniform(keys)) - if redefined - set_run_period!(ccd, keys[1], keys[end]) - end + set_run_period!(ccd, keys[1], keys[end]) end return set_dimension!(ccd, name, Dimension(keys)) @@ -596,11 +592,15 @@ end function _add_anonymous_dims!(md::ModelDef, comp_def::AbstractComponentDef) for (name, dim) in filter(pair -> pair[2] !== nothing, comp_def.dim_dict) - @info "Setting dimension $name to $dim" + # @info "Setting dimension $name to $dim" set_dimension!(md, name, dim) end end +@method function comps_dict!(obj::CompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) + obj.comps_dict = comps +end + """ add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) @@ -650,7 +650,7 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be if before === nothing && after === nothing _append_comp!(obj, comp_name, comp_def) # just add it to the end else - new_comps = OrderedDict{Symbol, ComponentDef}() + new_comps = OrderedDict{Symbol, AbstractComponentDef}() if before !== nothing if ! hascomp(obj, before) @@ -795,7 +795,7 @@ parameter connections should be maintained in the new component. filter!(epc -> !(epc in remove), external_param_conns(obj)) # Delete the old component from comps_dict, leaving the existing parameter connections - delete!(obj.ccd.comps_dict, comp_name) + delete!(obj.comps_dict, comp_name) else # Delete the old component and all its internal and external parameter connections delete!(obj, comp_name) diff --git a/src/core/delegate.jl b/src/core/delegate.jl index 11ea637be..d67bb2a50 100644 --- a/src/core/delegate.jl +++ b/src/core/delegate.jl @@ -63,82 +63,3 @@ macro delegate(ex, other=nothing) end return esc(result) end - -# _arg_default(t::Nothing) = nothing -# _arg_default(value::Symbol) = QuoteNode(value) -# _arg_default(value::Any) = value - -# function _compose_arg(arg_name, arg_type, slurp, default) -# decl = arg_name -# decl = arg_type != Any ? :($decl::$arg_type) : decl -# decl = slurp ? :($decl...) : decl -# decl = default !== nothing ? :($decl = $(_arg_default(default))) : decl -# return decl -# end - -# _compose_args(arg_tups::Vector{T}) where {T <: Tuple} = [_compose_arg(a...) for a in arg_tups] - -# N.B.: -# julia> splitdef(funcdef) -# Dict{Symbol,Any} with 5 entries: -# :name => :fname -# :args => Any[:(arg1::T)] # use splitarg() to bust out (arg_name, arg_type, slurp, default) -# :kwargs => Any[] -# :body => quote… -# :rtype => Any -# :whereparams => () - -# Example -#= -@macroexpand @Mimi.delegates( - function foo(cci::CompositeComponentInstance, bar, baz::Int, other::Float64=4.0; x=10) - println(other) - end, - - mi::ModelInstance, - m::Model -) - -=> - -quote - function foo(cci::CompositeComponentInstance, bar, baz::Int, other::Float64=4.0; x=10) - println(other) - end - function foo(mi::ModelInstance, bar, baz::Int, other::Float64=4.0; x=10)::Any - begin - return foo(mi.cci, bar, baz, other; x = x) - end - end - function foo(m::Model, bar, baz::Int, other::Float64=4.0; x=10)::Any - begin - return foo(m.mi, bar, baz, other; x = x) - end - end -end -=# -# macro delegates(args...) -# funcdef = args[1] -# args = collect(args[2:end]) - -# parts = splitdef(funcdef) -# fnargs = parts[:args] -# kwargs = parts[:kwargs] - -# result = quote $funcdef end # emit function as written, then add delegation funcs - -# for delegee in filter(x -> !(x isa LineNumberNode), args) -# (arg_name, arg_type, slurp, default) = splitarg(fnargs[1]) -# (delegee_name, arg_type, slurp, default) = splitarg(delegee) - -# fnargs[1] = :($delegee_name.$arg_name) -# call_args = [:($name) for (name, atype, slurp, default) in map(splitarg, fnargs)] -# call_kwargs = [:($name = $name) for (name, atype, slurp, default) in map(splitarg, kwargs)] - -# parts[:body] = quote return $(parts[:name])($(call_args...); $(call_kwargs...)) end -# fnargs[1] = delegee -# push!(result.args, MacroTools.combinedef(parts)) -# end - -# return esc(result) -# end diff --git a/src/core/instances.jl b/src/core/instances.jl index 5f82305c4..07fc00d57 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -165,7 +165,7 @@ Return the size of index `dim_name`` in model instance `mi`. @delegate dim_count(mi::ModelInstance, dim_name::Symbol) => md @method function reset_variables(ci::ComponentInstance) - # println("reset_variables($(ci.comp_id))") + # @info "reset_variables($(ci.comp_id))" vars = ci.variables for (name, T) in zip(names(vars), types(vars)) @@ -190,7 +190,8 @@ end return nothing end -@method function init(ci::ComponentInstance) +@method function init(ci::ComponentInstance, dims::DimValueDict) + # @info "init($(ci.comp_id))" reset_variables(ci) if ci.init != nothing @@ -206,29 +207,8 @@ end return nothing end -# @method function run_timestep(ci::ComponentInstance, clock::Clock) -# if ci.run_timestep !== nothing -# ci.run_timestep(parameters(ci), variables(ci), dim_value_dict(ci), clock.ts) -# end - -# # TBD: move this outside this func if components share a clock -# advance(clock) - -# return nothing -# end - -# @method function run_timestep(obj::CompositeComponentInstance, clock::Clock) -# for ci in components(obj) -# run_timestep(ci, clock) -# end -# return nothing -# end - @method _runnable(ci::ComponentInstance, clock::Clock) = (ci.first <= gettime(clock) <= ci.last) -# -# New versions -# @method function run_timestep(ci::ComponentInstance, clock::Clock, dims::DimValueDict) if ci.run_timestep !== nothing && _runnable(ci, clock) ci.run_timestep(ci.parameters, ci.variables, dims, clock.ts) @@ -246,41 +226,6 @@ end return nothing end -# -# TBD: might be obsolete -# -""" - function _make_clocks(ci::AbstractComponentInstance, time_keys::Vector{Int}) - -Store a vector of of Clocks into a composite instance's `clocks` member, -and repeat recursively through any subcomps. For non-composites, do nothing. -""" -# _make_clocks(ci::ComponentInstance, time_keys::Vector{Int}) = nothing - -# @method function _make_clocks(ci::CompositeComponentInstance, time_keys::Vector{Int}) -# clocks = ci.clocks # preallocated in constructor - -# if isuniform(time_keys) -# stepsize = step_size(time_keys) -# for (i, (first, last)) in enumerate(zip(ci.firsts, ci.lasts)) -# clocks[i] = Clock{FixedTimestep}(first, stepsize, last) -# end -# else -# for (i, (first, last)) in enumerate(zip(ci.firsts, ci.lasts)) -# first_index = findfirst(isequal(first), time_keys) -# last_index = findfirst(isequal(last), time_keys) -# times = Tuple(time_keys[first_index:last_index]) -# clocks[i] = Clock{VariableTimestep}(times) -# end -# end - -# for subcomp in components(ci) -# _make_clocks(subcomp, time_keys) -# end -# end - -# TBD: Write a reset(clock::Clock) method? - function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dimkeys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) @@ -301,7 +246,7 @@ function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dim_val_dict = DimValueDict(dim_dict(mi.md)) # recursively initializes all components - init(mi) + init(mi, dim_val_dict) clock = Clock(time_keys) while ! finished(clock) diff --git a/src/core/types.jl b/src/core/types.jl index 86e00215a..b5e16f52f 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -163,7 +163,7 @@ end struct ExternalParameterConnection <: AbstractConnection comp_name::Symbol param_name::Symbol # name of the parameter in the component - external_param::Symbol # name of the parameter stored in md.ccd.external_params + external_param::Symbol # name of the parameter stored in external_params end # @@ -319,8 +319,7 @@ end function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) self = (self === nothing ? new() : self) - anon = gensym(:anonymous) - comp_id = ComponentId(anon, anon) + comp_id = ComponentId(@__MODULE__, gensym(:anonymous)) comps = Vector{T where T <: AbstractComponentDef}() bindings = Vector{Pair{DatumReference, BindingTypes}}() exports = Vector{Pair{DatumReference, Symbol}}() @@ -470,9 +469,6 @@ end @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} - # firsts::Vector{Int} # in order corresponding with components - # lasts::Vector{Int} - # clocks::Vector{Clock} function CompositeComponentInstance(self::AbstractCompositeComponentInstance, comps::Vector{<: AbstractComponentInstance}, @@ -481,21 +477,13 @@ end name::Symbol=nameof(comp_def)) comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() - # pre-allocate these since we know the length - # count = length(comps) - # firsts = Vector{Int}(undef, count) - # lasts = Vector{Int}(undef, count) - # clocks = Vector{Clock}(undef, count) - - for (i, ci) in enumerate(comps) + for ci in comps comps_dict[ci.comp_name] = ci - # firsts[i] = ci.first - # lasts[i] = ci.last end (vars, pars) = _comp_instance_vars_pars(comps) ComponentInstance(self, comp_def, vars, pars, time_bounds, name) - CompositeComponentInstance(self, comps_dict) #, time_bounds, clocks) + CompositeComponentInstance(self, comps_dict) return self end diff --git a/test/test_composite.jl b/test/test_composite.jl index cf9e4b970..e95543814 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -15,7 +15,7 @@ reset_compdefs() var_1_1 = Variable(index=[time]) # computed function run_timestep(p, v, d, t) - @info "Comp1 run_timestep" + # @info "Comp1 run_timestep" v.var_1_1[t] = p.par_1_1[t] end end @@ -26,7 +26,7 @@ end var_2_1 = Variable(index=[time]) # computed function run_timestep(p, v, d, t) - @info "Comp2 run_timestep" + # @info "Comp2 run_timestep" v.var_2_1[t] = p.par_2_1[t] + p.par_2_2[t] end end @@ -36,7 +36,7 @@ end var_3_1 = Variable(index=[time]) # external output function run_timestep(p, v, d, t) - @info "Comp3 run_timestep" + # @info "Comp3 run_timestep" v.var_3_1[t] = p.par_3_1[t] * 2 end end diff --git a/test/test_dependencies.jl b/test/test_dependencies.jl index 161fb61b7..b6e4cd288 100644 --- a/test/test_dependencies.jl +++ b/test/test_dependencies.jl @@ -70,6 +70,6 @@ function run_dependency_tests(dependencies=dependencies) if num_errors > 0 error(error_message) else - println("All dependency tests passed.") + @info "All dependency tests passed." end end diff --git a/test/test_main_variabletimestep.jl b/test/test_main_variabletimestep.jl index ace8f6663..fa9603cd4 100644 --- a/test/test_main_variabletimestep.jl +++ b/test/test_main_variabletimestep.jl @@ -6,7 +6,7 @@ using Mimi import Mimi: reset_compdefs, reset_variables, @defmodel, variable, variable_names, external_param, build, - compdef, compdefs, dimensions, dimension, compinstance + compdef, compdefs, dimension, compinstance reset_compdefs() diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 0bf0b7033..eceb21c92 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - compdef, reset_compdefs, first_period, last_period + compdef, compname, reset_compdefs, compmodule, first_period, last_period reset_compdefs() diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index 2503ba86e..fdd4817c8 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - compdef, reset_compdefs, first_period, last_period + compdef, reset_compdefs, first_period, last_period, compmodule, compname reset_compdefs() diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 17439a221..af6b7df21 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -7,9 +7,8 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, - reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, - modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, - dimensions, compdefs + reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, + modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs reset_compdefs() @@ -170,7 +169,6 @@ run(m) # This results in 2 warnings, so we test for both. @test_logs( (:warn, "Redefining dimension :time"), - # (:warn, "Resetting E component's last timestep to 2015"), set_dimension!(m, :time, [2015]) ) diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index f69a712b5..9fe86c618 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -8,7 +8,7 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, - compdef, getproperty, setproperty!, dimension, dimensions, compdefs + dim_names, compdef, getproperty, setproperty!, dimension, compdefs reset_compdefs() diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 01e24534c..1e623155e 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -93,8 +93,8 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for @test nameof(compdef(m.md, :X)) == :bad3 # The replacement was still successful #external_param_conns(md, comp_name) -@test length(external_param_conns(m.md)) == 0 # The external paramter connection was removed -@test length(external_params(m.md)) == 1 # The external parameter still exists +@test length(m.md.external_param_conns) == 0 # The external parameter connection was removed +@test length(m.md.external_params) == 1 # The external parameter still exists # 5. Test bad external parameter dimensions From 05473aedaa62e1317fa0deb227f26c93bfbc0c73 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sun, 3 Feb 2019 16:11:37 -0800 Subject: [PATCH 30/81] WIP - Debugging composites --- src/core/connections.jl | 8 ++++---- src/core/defs.jl | 4 ++-- src/core/types.jl | 1 + test/test_parametertypes.jl | 8 ++++---- test/test_replace_comp.jl | 8 ++++---- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 44ea86f8c..cf90981f8 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -233,11 +233,11 @@ the dictionary keys are strings that match the names of unset parameters in the function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T parameters = Dict(k => v for (k, v) in parameters) leftovers = unconnected_params(md) - external_params = external_params(md) + ext_params = external_params(md) for (comp_name, param_name) in leftovers # check whether we need to set the external parameter - if ! haskey(external_params, param_name) + if ! haskey(ext_params, param_name) value = parameters[string(param_name)] param_dims = parameter_dimensions(md, comp_name, param_name) @@ -372,14 +372,14 @@ function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; rai if update_timesteps && raise_error error("Cannot update timesteps; parameter $name is a scalar parameter.") end - _update_scalar_param!(param, value) + _update_scalar_param!(param, name, value) else _update_array_param!(md, name, value, update_timesteps, raise_error) end end -function _update_scalar_param!(param::ScalarModelParameter, value) +function _update_scalar_param!(param::ScalarModelParameter, name, value) if ! (value isa typeof(param.value)) try value = convert(typeof(param.value), value) diff --git a/src/core/defs.jl b/src/core/defs.jl index 260722934..3e42192a6 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -621,11 +621,11 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be time_index = dim_keys(obj, :time) if first !== nothing && first < time_index[1] - error("Cannot add component $name with first time before first of model's time index range.") + error("Cannot add component $comp_name with first time before first of model's time index range.") end if last !== nothing && last > time_index[end] - error("Cannot add component $name with last time after end of model's time index range.") + error("Cannot add component $comp_name with last time after end of model's time index range.") end if before !== nothing && after !== nothing diff --git a/src/core/types.jl b/src/core/types.jl index b5e16f52f..5b5df157a 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -330,6 +330,7 @@ end @method internal_param_conns(obj::CompositeComponentDef) = obj.internal_param_conns @method external_param_conns(obj::CompositeComponentDef) = obj.external_param_conns +@method external_params(obj::CompositeComponentDef) = obj.external_params @method external_param(obj::CompositeComponentDef, name::Symbol) = obj.external_params[name] @method add_backup!(obj::CompositeComponentDef, backup) = push!(obj.backups, backup) diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index bee08a154..ab3ec0e7a 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -4,8 +4,8 @@ using Mimi using Test import Mimi: - external_params, TimestepMatrix, TimestepVector, ArrayModelParameter, - ScalarModelParameter, FixedTimestep, reset_compdefs + external_params, external_param, TimestepMatrix, TimestepVector, + ArrayModelParameter, ScalarModelParameter, FixedTimestep, reset_compdefs reset_compdefs() @@ -128,7 +128,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) @test_logs( (:warn, "Redefining dimension :time"), - (:warn, "Resetting MyComp2 component's first timestep to 2001"), + # (:warn, "Resetting MyComp2 component's first timestep to 2001"), set_dimension!(m, :time, 2001:2003) ) @@ -158,7 +158,7 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) @test_logs( (:warn, "Redefining dimension :time"), - (:warn, "Resetting MyComp2 component's first timestep to 2005"), + # (:warn, "Resetting MyComp2 component's first timestep to 2005"), set_dimension!(m, :time, [2005, 2020, 2050]) ) diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 1e623155e..b7113eecb 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -3,7 +3,7 @@ module TestReplaceComp using Test using Mimi import Mimi: - reset_compdefs, compdefs + reset_compdefs, compdefs, compdef reset_compdefs() @@ -91,10 +91,10 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs (:warn, r".*parameter x no longer exists in component.*") replace_comp!(m, bad3, :X) -@test nameof(compdef(m.md, :X)) == :bad3 # The replacement was still successful +@test nameof(compdef(m.md, :X)) == :bad3 # The replacement was still successful #external_param_conns(md, comp_name) -@test length(m.md.external_param_conns) == 0 # The external parameter connection was removed -@test length(m.md.external_params) == 1 # The external parameter still exists +@test length(external_param_conns(m)) == 0 # The external parameter connection was removed +@test length(external_params(m)) == 1 # The external parameter still exists # 5. Test bad external parameter dimensions From ea37c6028dab6c20d6a61621459ad32737280cbe Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 5 Feb 2019 09:41:54 -0800 Subject: [PATCH 31/81] WIP - Composite Components - Added show.jl with show(io:IO, ...) methods to present Mimi objects in a readable manner. - Added abstract supertypes MimiStruct and MimiClass to simplify show methods - Created test/test_show.jl --- src/Mimi.jl | 1 + src/core/defs.jl | 8 +-- src/core/types.jl | 36 ++++++----- src/utils/graph.jl | 5 +- src/utils/show.jl | 124 ++++++++++++++++++++++++++++++++++++++ test/test_replace_comp.jl | 2 +- test/test_show.jl | 113 ++++++++++++++++++++++++++++++++++ 7 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 src/utils/show.jl create mode 100644 test/test_show.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index d0c1e3a47..5416ab833 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -66,6 +66,7 @@ include("utils/graph.jl") include("utils/lint_helper.jl") include("utils/misc.jl") include("utils/plotting.jl") +include("utils/show.jl") """ load_comps(dirname::String="./components") diff --git a/src/core/defs.jl b/src/core/defs.jl index 3e42192a6..facd959c3 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -71,10 +71,6 @@ end is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), nameof(dr)) is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), nameof(dr)) -function Base.show(io::IO, comp_id::ComponentId) - print(io, "") -end - number_type(md::ModelDef) = md.number_type # TBD: should be numcomps() @@ -637,8 +633,8 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be error("Cannot add two components of the same name ($comp_name)") end - # Create a shallow copy of the original but with the new name - # TBD: Why do we need to make a copy here? Sort this out. + # Create a deepcopy of the original but with the new name so + # it has separate variables and parameters, etc. if compname(comp_def.comp_id) != comp_name comp_def = copy_comp_def(comp_def, comp_name) end diff --git a/src/core/types.jl b/src/core/types.jl index 5b5df157a..a3c70a651 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,11 +1,17 @@ using Classes using DataStructures +# Having all our structs/classes subtype these simplifies "show" methods +abstract type MimiStruct end +@class MimiClass <: Class + +const AbstractMimiType = Union{MimiStruct, AbstractMimiClass} + # # 1. Types supporting parameterized Timestep and Clock objects # -abstract type AbstractTimestep end +abstract type AbstractTimestep <: MimiStruct end struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep t::Int @@ -25,7 +31,7 @@ struct VariableTimestep{TIMES} <: AbstractTimestep end end -mutable struct Clock{T <: AbstractTimestep} +mutable struct Clock{T <: AbstractTimestep} <: MimiStruct ts::T function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T @@ -37,7 +43,7 @@ mutable struct Clock{T <: AbstractTimestep} end end -mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} +mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} <: MimiStruct data::Array{T, N} function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} @@ -59,7 +65,7 @@ const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1} # 2. Dimensions # -abstract type AbstractDimension end +abstract type AbstractDimension <: MimiStruct end const DimensionKeyTypes = Union{AbstractString, Symbol, Int, Float64} const DimensionRangeTypes = Union{UnitRange{Int}, StepRange{Int, Int}} @@ -97,7 +103,7 @@ mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension # # 3. Types supporting Parameters and their connections # -abstract type ModelParameter end +abstract type ModelParameter <: MimiStruct end # TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? @@ -142,7 +148,7 @@ dim_names(obj::ArrayModelParameter) = obj.dim_names dim_names(obj::ScalarModelParameter) = [] -abstract type AbstractConnection end +abstract type AbstractConnection <: MimiStruct end struct InternalParameterConnection <: AbstractConnection src_comp_name::Symbol @@ -173,7 +179,7 @@ end # To identify components, we create a variable with the name of the component # whose value is an instance of this type, e.g. # const global adder = ComponentId(module_name, comp_name) -struct ComponentId +struct ComponentId <: MimiStruct module_name::Symbol comp_name::Symbol end @@ -186,7 +192,7 @@ ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) # # Objects with a `name` attribute -@class NamedObj begin +@class NamedObj <: MimiClass begin name::Symbol end @@ -354,7 +360,7 @@ end # # Supertype for variables and parameters in component instances -@class ComponentInstanceData{NT <: NamedTuple} begin +@class ComponentInstanceData{NT <: NamedTuple} <: MimiClass begin nt::NT end @@ -378,7 +384,7 @@ end # A container class that wraps the dimension dictionary when passed to run_timestep() # and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimValueDict +struct DimValueDict <: MimiStruct dict::Dict{Symbol, Vector{Int}} function DimValueDict(dim_dict::AbstractDict) @@ -392,7 +398,7 @@ end # as the "d" parameter. Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} begin +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: MimiClass begin comp_name::Symbol comp_id::ComponentId variables::TV @@ -566,7 +572,7 @@ This `Model` can be created with the optional keyword argument `number_type` ind the default type of number used for the `ModelDef`. If not specified the `Model` assumes a `number_type` of `Float64`. """ -mutable struct Model +mutable struct Model <: MimiStruct md::ModelDef mi::Union{Nothing, ModelInstance} @@ -586,7 +592,7 @@ end A Mimi `Model` whose results are obtained by subtracting results of one `base` Model from those of another `marginal` Model` that has a difference of `delta`. """ -struct MarginalModel +struct MarginalModel <: MimiStruct base::Model marginal::Model delta::Float64 @@ -609,7 +615,7 @@ end A container for a component, for interacting with it within a model. """ -struct ComponentReference +struct ComponentReference <: MimiStruct model::Model comp_name::Symbol end @@ -620,7 +626,7 @@ end A container for a variable within a component, to improve connect_param! aesthetics, by supporting subscripting notation via getindex & setindex . """ -struct VariableReference +struct VariableReference <: MimiStruct model::Model comp_name::Symbol var_name::Symbol diff --git a/src/utils/graph.jl b/src/utils/graph.jl index 361105962..ce4a61af1 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -2,7 +2,6 @@ # Graph Functionality # - function _show_conns(io, m, comp_name, which::Symbol) datumtype = which == :incoming ? "parameters" : "variables" println(io, " $which $datumtype:") @@ -22,7 +21,9 @@ function _show_conns(io, m, comp_name, which::Symbol) end end -function show(io::IO, m::Model) +show_conns(m::Model) = show_conns(stdout, m) + +function show_conns(io::IO, m::Model) println(io, "Model component connections:") for (i, comp_name) in enumerate(compkeys(m.md)) diff --git a/src/utils/show.jl b/src/utils/show.jl new file mode 100644 index 000000000..9db9b8a03 --- /dev/null +++ b/src/utils/show.jl @@ -0,0 +1,124 @@ +# +# show() methods to make output of complex structures readable +# + +# printstyled(IOContext(io, :color => true), "string", color=:red) + +import Base: show + +spaces = " " + +function _indent_level!(io::IO, delta::Int) + level = get(io, :indent_level, 0) + return IOContext(io, :indent_level => max(level + delta, 0)) +end + +indent(io::IO) = _indent_level!(io, 1) +outdent(io::IO) = _indent_level!(io, -1) + +function print_indented(io::IO, args...) + level = get(io, :indent_level, 0) + print(io, repeat(spaces, level), args...) + nothing +end + +function show(io::IO, obj::ComponentId) + print(io, "") + nothing +end + +function show(io::IO, obj::AbstractDimension) + print(io, keys(obj)) + nothing +end + +function _show_field(io::IO, name::Symbol, value; show_empty=true) + if !show_empty && isempty(value) + return + end + + print(io, "\n") + print_indented(io, name, ": ") + show(io, value) +end + +function _show_field(io::IO, name::Symbol, dict::AbstractDict; show_empty=true) + if !show_empty && isempty(dict) + return + end + + print(io, "\n") + print_indented(io, name, ": ", typeof(dict)) + io = indent(io) + for (k, v) in dict + print(io, "\n") + print_indented(io, k, " => ") + show(io, v) + end + nothing +end + +function _show_field(io::IO, name::Symbol, vec::Vector{T}; show_empty=true) where T + count = length(vec) + ellipsis = false + max_shown = 5 + if count > max_shown + last = vec[end] + vec = vec[1:max_shown-1] + ellipsis = true + end + io = indent(io) + for (i, value) in enumerate(vec) + print(io, "\n") + print_indented(io, "$i: ", value) + end + if ellipsis + print(io, "\n") + print_indented(io, "...\n") + print_indented(io, "$count: ") + show(io, last) + end + nothing +end + +function _show_fields(io::IO, obj, names; show_empty=true) + for name in names + value = getfield(obj, name) + _show_field(io, name, value) + end + nothing +end + +function _show_datum_def(io::IO, obj::AbstractDatumDef) + print(io, typeof(obj), "($(obj.name)::$(obj.datatype))") + io = indent(io) + + _show_field(io, :dim_names, obj.dim_names) + + for field in (:description, :unit) + _show_field(io, field, getfield(obj, field), show_empty=false) + end + nothing +end + +show(io::IO, obj::VariableDef) = _show_datum_def(io, obj) + +function show(io::IO, obj::ParameterDef) + _show_datum_def(io, obj) + _show_field(indent(io), :default, obj.default) +end + +function show(io::IO, obj::AbstractMimiType) + print(io, typeof(obj)) + + # If a name is present, print it as type(name) + fields = fieldnames(typeof(obj)) + pos = findfirst(x -> x == :name, fields) + + if pos !== nothing + print(io, "($(obj.name))") + fields = deleteat!([fields...], pos) + end + _show_fields(indent(io), obj, fields) + nothing +end diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index b7113eecb..66ebda839 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -3,7 +3,7 @@ module TestReplaceComp using Test using Mimi import Mimi: - reset_compdefs, compdefs, compdef + reset_compdefs, compdefs, compdef, external_param_conns reset_compdefs() diff --git a/test/test_show.jl b/test/test_show.jl new file mode 100644 index 000000000..d98163f1b --- /dev/null +++ b/test/test_show.jl @@ -0,0 +1,113 @@ +module TestShow + +using Test +using Mimi +import Mimi: + reset_compdefs, compdefs, compdef, ComponentId, MimiStruct, ParameterDef + +# reset_compdefs() + +@defcomp X begin + x = Parameter(index = [time]) + y = Variable(index = [time]) + function run_timestep(p, v, d, t) + v.y[t] = 1 + end +end + +function test_show(obj, expected::AbstractString) + buf = IOBuffer() + show(buf, obj) + @test String(take!(buf)) == expected +end + +function showstr(obj) + buf = IOBuffer() + show(buf, obj) + String(take!(buf)) +end + +compid = ComponentId(TestShow, :something) +test_show(compid, "") + +struct Foo <: MimiStruct + a::Dict + b::Int + c::Float64 +end + +foo = Foo(Dict((:x=>10, :y=>20)), 4, 44.4) +# Use typeof(Foo) so it works in test mode and using include(), which results in Main.TestShow.Foo. +# test_show(foo, "$(typeof(foo))\n a: Dict{Symbol,Int64}\n y => 20\n x => 10\n b: 4\n c: 44.4") + +# (:name, :datatype, :dim_names, :description, :unit, :default) +p = ParameterDef(:v1, Float64, [:time], "description string", "Mg C", 101) +# test_show(p, "ParameterDef\n name: :v1\n datatype: Float64\n dim_names: Symbol[:time]\n description: \"description string\"\n unit: \"Mg C\"\n default: 101") + +p = ParameterDef(:v1, Float64, [:time], "", "", nothing) +# test_show(p, "ParameterDef\n name: :v1\n datatype: Float64\n dim_names: Symbol[:time]\n default: nothing") + +m = Model() +set_dimension!(m, :time, 2000:2005) +add_comp!(m, X) # Original component X +set_param!(m, :X, :x, zeros(6)) + +expected = """ +Model + md: ModelDef(##anonymous#) + comp_id: + variables: OrderedCollections.OrderedDict{Symbol,VariableDef} + parameters: OrderedCollections.OrderedDict{Symbol,ParameterDef} + dim_dict: OrderedCollections.OrderedDict{Symbol,Union{Nothing, Dimension}} + time => [2000, 2001, 2002, 2003, 2004, 2005] + first: nothing + last: nothing + is_uniform: true + comps_dict: OrderedCollections.OrderedDict{Symbol,AbstractComponentDef} + X => ComponentDef(X) + comp_id: + variables: OrderedCollections.OrderedDict{Symbol,VariableDef} + y => VariableDef(y::Number) + 1: time + parameters: OrderedCollections.OrderedDict{Symbol,ParameterDef} + x => ParameterDef(x::Number) + 1: time + default: nothing + dim_dict: OrderedCollections.OrderedDict{Symbol,Union{Nothing, Dimension}} + time => nothing + first: nothing + last: nothing + is_uniform: true + 1: ExternalParameterConnection + comp_name: :X + param_name: :x + external_param: :x + external_params: Dict{Symbol,ModelParameter} + x => ArrayModelParameter{TimestepArray{FixedTimestep{2000,1,LAST} where LAST,Float64,1}} + values: TimestepArray{FixedTimestep{2000,1,LAST} where LAST,Float64,1} + 1: 0.0 + 2: 0.0 + 3: 0.0 + 4: 0.0 + ... + 6: 0.0 + 1: time + sorted_comps: nothing + number_type: Float64 + mi: nothing""" # ignore (most) whitespace + +# Quote regex special characters +# Modified from https://github.com/JuliaLang/julia/issues/6124 +function quotemeta(s::AbstractString) + res = replace(s, r"([()[\]{}?*\$.&~#=!<>|:])" => s"\\\1") + replace(res, "\0" => "\\0") +end + +re = quotemeta(expected) + +# remove the number in the gensym since it changes each run +output = replace(showstr(m), r"(##anonymous#)\d+" => s"\1") + +@test match(Regex(re), output) !== nothing + +end # module From 1a187038cdff016ae19aeb46485aba384f723fa8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 8 Feb 2019 09:40:01 -0800 Subject: [PATCH 32/81] - Temporarily disabled loading of non-core files for debugging - Moved show.jl into core - Created dirty!() to mark a ModelDef as changed, dropped decache(m::Model) - Added module name into generated run_timestep name to allow multiple uses of a component name in different composites - add_comp!() now always adds a deepcopy'd comp def - Created type alias ComponentPath to store a tuple of symbols identifying a sub-component in composite tree, plus related functions --- src/Mimi.jl | 22 +++--- src/core/build.jl | 50 +++++++------ src/core/connections.jl | 12 +++- src/core/defcomp.jl | 13 ++-- src/core/defcomposite.jl | 2 +- src/core/defs.jl | 140 +++++++++++++++++++++++++----------- src/core/delegate.jl | 7 +- src/core/instances.jl | 2 + src/core/model.jl | 54 ++++++-------- src/{utils => core}/show.jl | 44 +++++++----- src/core/types.jl | 128 +++++++++++++++++++++------------ test/test_replace_comp.jl | 2 - 12 files changed, 293 insertions(+), 183 deletions(-) rename src/{utils => core}/show.jl (85%) diff --git a/src/Mimi.jl b/src/Mimi.jl index 5416ab833..05427dc16 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -14,7 +14,7 @@ export @defmcs, MarginalModel, Model, - add_comp!, + add_comp!, components, connect_param!, create_marginal_model, @@ -45,8 +45,8 @@ export update_params!, variables -include("core/types.jl") include("core/delegate.jl") +include("core/types.jl") # After loading types and delegation macro, the rest is alphabetical include("core/build.jl") @@ -59,14 +59,16 @@ include("core/instances.jl") include("core/references.jl") include("core/time.jl") include("core/model.jl") -include("explorer/explore.jl") -include("mcs/mcs.jl") -include("utils/getdataframe.jl") -include("utils/graph.jl") -include("utils/lint_helper.jl") -include("utils/misc.jl") -include("utils/plotting.jl") -include("utils/show.jl") +include("core/show.jl") + +# For debugging composites we don't need these +#include("explorer/explore.jl") +#include("mcs/mcs.jl") +#include("utils/getdataframe.jl") +#include("utils/graph.jl") +#include("utils/lint_helper.jl") +#include("utils/misc.jl") +#include("utils/plotting.jl") """ load_comps(dirname::String="./components") diff --git a/src/core/build.jl b/src/core/build.jl index 6c0ce3dd1..f23a02f83 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -63,49 +63,51 @@ Instantiate a component `comp_def` in the model `md` and its variables (but not parameters). Return the resulting ComponentInstanceVariables. """ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) - comp_name = nameof(comp_def) - var_defs = variables(comp_def) + var_defs = variables(comp_def) - names = ([nameof(vdef) for vdef in var_defs]...,) - types = Tuple{[_instance_datatype(md, vdef) for vdef in var_defs]...} - values = [_instantiate_datum(md, def) for def in var_defs] + names = Symbol[nameof(def) for def in var_defs] + values = Any[_instantiate_datum(md, def) for def in var_defs] + types = DataType[_instance_datatype(md, def) for def in var_defs] + paths = repeat(Any[comp_def.comp_path], length(names)) - return ComponentInstanceVariables(names, types, values) + return ComponentInstanceVariables(names, types, values, paths) end # Create ComponentInstanceVariables for a composite component from the list of exported vars @method function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) - names = [] - values = [] + names = Symbol[] + values = Any[] - for (dr, name) in comp_def.exports + for (name, dr) in comp_def.exports if is_variable(dr) - obj = var_dict[dr.comp_id.comp_name] # TBD: should var_dict hash on ComponentId instead? + obj = var_dict[compname(dr)] value = getproperty(obj, nameof(dr)) push!(names, name) push!(values, value) end end - types = map(typeof, values) - return ComponentInstanceVariables(Tuple(names), Tuple{types...}, Tuple(values)) + types = DataType[typeof(val) for val in values] + paths = repeat(Any[comp_def.comp_path], length(names)) + return ComponentInstanceVariables(names, types, values, paths) end @method function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - names = [] - values = [] + names = Symbol[] + values = Any[] - for (dr, name) in comp_def.exports + for (name, dr) in comp_def.exports if is_parameter(dr) - d = par_dict[dr.comp_id.comp_name] # TBD: should par_dict hash on ComponentId instead? + d = par_dict[compname(dr)] value = d[nameof(dr)] push!(names, name) push!(values, value) end end - types = map(typeof, values) - return ComponentInstanceParameters(Tuple(names), Tuple{types...}, Tuple(values)) + paths = repeat(Any[comp_def.comp_path], length(names)) + types = DataType[typeof(val) for val in values] + return ComponentInstanceParameters(names, types, values, paths) end function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @@ -176,15 +178,15 @@ end function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) # @info "Instantiating params for $(comp_def.comp_id)" - comp_name = nameof(comp_def) d = par_dict[comp_name] - pnames = Tuple(parameter_names(comp_def)) - pvals = [d[pname] for pname in pnames] - ptypes = Tuple{map(typeof, pvals)...} + names = parameter_names(comp_def) + vals = Any[d[name] for name in names] + types = DataType[typeof(val) for val in vals] + paths = repeat([comp_def.comp_path], length(names)) - return ComponentInstanceParameters(pnames, ptypes, pvals) + return ComponentInstanceParameters(names, types, vals, paths) end @method function _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) @@ -230,6 +232,7 @@ function _build(md::ModelDef) error(msg) end + # TBD: key by ComponentPath since these span levels var_dict = Dict{Symbol, Any}() # collect all var defs and par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go @@ -251,6 +254,7 @@ end function build(m::Model) # Reference a copy in the ModelInstance to avoid changes underfoot m.mi = _build(deepcopy(m.md)) + m.md.dirty = false return nothing end diff --git a/src/core/connections.jl b/src/core/connections.jl index cf90981f8..d4868250c 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -11,6 +11,7 @@ function disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol) # println("disconnect_param!($comp_name, $param_name)") filter!(x -> !(x.dst_comp_name == comp_name && x.dst_par_name == param_name), internal_param_conns(md)) filter!(x -> !(x.comp_name == comp_name && x.param_name == param_name), external_param_conns(md)) + dirty!(md) end # Default string, string unit check function @@ -73,7 +74,7 @@ function connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext _check_labels(md, comp_def, param_name, ext_param) end - disconnect_param!(md, comp_name, param_name) + disconnect_param!(md, comp_name, param_name) # calls dirty!() conn = ExternalParameterConnection(comp_name, param_name, ext_param_name) add_external_param_conn!(md, conn) @@ -99,7 +100,7 @@ function connect_param!(md::ModelDef, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) # remove any existing connections for this dst parameter - disconnect_param!(md, dst_comp_name, dst_par_name) + disconnect_param!(md, dst_comp_name, dst_par_name) # calls dirty!() dst_comp_def = compdef(md, dst_comp_name) src_comp_def = compdef(md, src_comp_name) @@ -273,14 +274,17 @@ end @method function add_internal_param_conn!(obj::CompositeComponentDef, conn::InternalParameterConnection) push!(obj.internal_param_conns, conn) + dirty!(obj) end @method function add_external_param_conn!(obj::CompositeComponentDef, conn::ExternalParameterConnection) push!(obj.external_param_conns, conn) + dirty!(obj) end @method function set_external_param!(obj::CompositeComponentDef, name::Symbol, value::ModelParameter) obj.external_params[name] = value + dirty!(obj) end function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) @@ -377,6 +381,7 @@ function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; rai _update_array_param!(md, name, value, update_timesteps, raise_error) end + dirty!(md) end function _update_scalar_param!(param::ScalarModelParameter, name, value) @@ -436,6 +441,7 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise param.values = value end end + dirty!(md) nothing end @@ -480,7 +486,7 @@ function add_connector_comps(md::ModelDef) # Fetch the definition of the appropriate connector commponent conn_name = num_dims == 1 ? :ConnectorCompVector : :ConnectorCompMatrix conn_comp_def = compdef(conn_name) - conn_comp_name = connector_comp_name(i) + conn_comp_name = connector_comp_name(i) # generate a new name # Add the connector component before the user-defined component that required it # println("add_connector_comps: add_comp!(md, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)") diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 551d672b9..60ea4dc08 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -16,7 +16,7 @@ const global built_in_comps = (:adder, :ConnectorCompVector, :ConnectorCompMatr is_builtin(comp_name) = comp_name in built_in_comps -function _generate_run_func(comp_name, args, body) +function _generate_run_func(comp_name, module_name, args, body) if length(args) != 4 error("Can't generate run_timestep; requires 4 arguments but got $args") end @@ -25,7 +25,7 @@ function _generate_run_func(comp_name, args, body) # Generate unique function name for each component so we can store a function pointer. # (Methods requiring dispatch cannot be invoked directly. Could use FunctionWrapper here...) - func_name = Symbol("run_timestep_$comp_name") + func_name = Symbol("run_timestep_$(module_name)_$(comp_name)") # Needs "global" so function is defined outside the "let" statement func = :( @@ -40,7 +40,7 @@ function _generate_run_func(comp_name, args, body) return func end -function _generate_init_func(comp_name, args, body) +function _generate_init_func(comp_name, module_name, args, body) if length(args) != 3 error("Can't generate init function; requires 3 arguments but got $args") @@ -49,7 +49,7 @@ function _generate_init_func(comp_name, args, body) # add types to the parameters (p, v, d) = args - func_name = Symbol("init_$comp_name") + func_name = Symbol("init_$(module_name)_$(comp_name)") func = :( global function $(func_name)($(p)::Mimi.ComponentInstanceParameters, @@ -159,10 +159,10 @@ macro defcomp(comp_name, ex) if @capture(elt, function fname_(args__) body__ end) if fname == :run_timestep - expr = _generate_run_func(comp_name, args, body) + expr = _generate_run_func(comp_name, nameof(__module__), args, body) elseif fname == :init - expr = _generate_init_func(comp_name, args, body) + expr = _generate_init_func(comp_name, nameof(__module__), args, body) else error("@defcomp can contain only these functions: init(p, v, d) and run_timestep(p, v, d, t)") end @@ -272,6 +272,7 @@ macro defmodel(model_name, ex) @capture(ex, elements__) # @__MODULE__ is evaluated in calling module when macro is interpreted + # TBD: simplify using __module__ ? result = :( let calling_module = @__MODULE__, comp_mod_name = nothing global $model_name = Model() diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index d0b28f2fe..5ad948866 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -57,7 +57,7 @@ are all variations on `component(...)`, which adds a component to the composite. calling signature for `component()` processed herein is: `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; - exports::Union{Nothing,Vector}, bindings::Union{Nothing,Vector{Pair}})` + exports::Union{Nothing,ExportsDef}, bindings::Union{Nothing,BindingsDef})` In this macro, the vector of symbols to export is expressed without the `:`, e.g., `exports=[var_1, var_2, param_1])`. The names must be variable or parameter names in diff --git a/src/core/defs.jl b/src/core/defs.jl index facd959c3..5f35b55cc 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -18,30 +18,63 @@ function compdef(comp_name::Symbol) end end +@delegate compdef(dr::DatumReference) => comp_id + # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] @method compdefs(c::CompositeComponentDef) = values(c.comps_dict) @method compkeys(c::CompositeComponentDef) = keys(c.comps_dict) -@method hascomp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) +@method has_comp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) @method compdef(c::CompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] -# Return the module object for the component was defined in compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name @method compmodule(obj::ComponentDef) = compmodule(obj.comp_id) @method compname(obj::ComponentDef) = compname(obj.comp_id) +compnames() = map(compname, compdefs()) + function reset_compdefs(reload_builtins=true) empty!(_compdefs) if reload_builtins - compdir = joinpath(@__DIR__, "..", "components") + compdir = joinpath(@__DIR__, "..", "components") load_comps(compdir) end end +_append_path(path::Union{Nothing, ComponentPath}, name::Symbol) = (path === nothing ? (name,) : (path..., name)) + +@method function comp_path!(parent::CompositeComponentDef, child::AbstractComponentDef) + child.comp_path = _append_path(parent.comp_path, child.name) + # @info "Setting comp path to $(child.comp_path)" +end + +dirty(md::ModelDef) = md.dirty + +@method function dirty!(obj::ComponentDef) + path = obj.comp_path + if (path === nothing || isempty(path)) + return + end + + root = compdef(path[1]) + + # test is necessary to avoid looping when length(path) == 1 + if root isa ModelDef + dirty!(root) + end +end + +dirty!(md::ModelDef) = (md.dirty = true) + +@method function Base.parent(obj::ComponentDef) + parent_path = parent(obj.comp_path) + return compdef(parent_path) +end + first_period(comp_def::ComponentDef) = comp_def.first last_period(comp_def::ComponentDef) = comp_def.last @@ -77,18 +110,6 @@ number_type(md::ModelDef) = md.number_type @method numcomponents(obj::ComponentDef) = 0 # no sub-components @method numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) -function dumpcomps() - for comp in compdefs() - println("\n$(nameof(comp))") - for (tag, objs) in ((:Variables, variables(comp)), (:Parameters, parameters(comp)), (:Dimensions, dim_dict(comp))) - println(" $tag") - for obj in objs - println(" $(nameof(obj)) = $obj") - end - end - end -end - """ new_comp(comp_id::ComponentId, verbose::Bool=true) @@ -111,12 +132,12 @@ function new_comp(comp_id::ComponentId, verbose::Bool=true) end """ - delete!(m::ModelDef, component::Symbol) + delete!(obj::CompositeComponentDef, component::Symbol) Delete a `component` by name from a model definition `m`. """ @method function Base.delete!(ccd::CompositeComponentDef, comp_name::Symbol) - if ! hascomp(ccd, comp_name) + if ! has_comp(ccd, comp_name) error("Cannot delete '$comp_name': component does not exist.") end @@ -140,6 +161,7 @@ end comp.dim_dict[Symbol(name)] = dim # TBD: test this end +# Note that this operates on the registered comp, not one added to a composite add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) @method function dim_names(ccd::CompositeComponentDef) @@ -253,15 +275,22 @@ Composites recurse on sub-components. #_show_run_period(obj, first, last) first_per = first_period(obj) last_per = last_period(obj) + changed = false if first_per !== nothing && first_per < first @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" obj.first = first + changed = true end if last_per !== nothing && last_per > last @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" obj.last = last + changed = true + end + + if changed + dirty!(obj) end # N.B. compdefs() returns an empty list for leaf ComponentDefs @@ -293,6 +322,7 @@ an integer; or to the values in the vector or range if `keys` is either of those end @method function set_dimension!(obj::CompositeComponentDef, name::Symbol, dim::Dimension) + dirty!(obj) obj.dim_dict[name] = dim end @@ -326,6 +356,7 @@ end @method function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, datatype, dimensions, description, unit, default) comp_def.parameters[name] = p + dirty!(comp_def) return p end @@ -345,7 +376,7 @@ Return a list of the parameter definitions for `comp_def`. # return cached parameters, if any if length(pars) == 0 - for (dr, name) in ccd.exports + for (name, dr) in ccd.exports cd = compdef(dr.comp_id) if has_parameter(cd, nameof(dr)) pars[name] = parameter(cd, nameof(dr)) @@ -379,7 +410,7 @@ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, c @method parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), nameof(dr)) -@method function parameter(obj::ComponentDef, name::Symbol) +@method function _parameter(obj::ComponentDef, name::Symbol) try return obj.parameters[name] catch @@ -387,6 +418,18 @@ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, c end end +function parameter(obj::ComponentDef, name::Symbol) + _parameter(obj, name) +end + +@method function parameter(obj::CompositeComponentDef, name::Symbol) + if ! is_exported(obj, name) + error("Parameter $name is not exported by composite component $(obj.comp_path)") + end + _parameter(obj, name) +end + + @method has_parameter(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.parameters, name) @method function parameter_unit(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) @@ -463,6 +506,7 @@ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, set_external_scalar_param!(md, param_name, value) end + # connect_param! calls dirty! so we don't have to connect_param!(md, comp_name, param_name, param_name) nothing end @@ -472,6 +516,7 @@ end # @method variables(comp_def::ComponentDef) = values(comp_def.variables) +# TBD: if we maintain vars/pars dynamically, this can be dropped @method function variables(ccd::CompositeComponentDef) vars = ccd.variables @@ -492,14 +537,17 @@ variables(comp_id::ComponentId) = variables(compdef(comp_id)) variables(dr::DatumReference) = variables(dr.comp_id) +# TBD: Perhaps define _variable to behave as below, and have the public version +# check it's exported before returning it. (Could error("exists but not exported?")) @method function variable(comp_def::ComponentDef, var_name::Symbol) + # TBD test this can be dropped if we maintain vars/pars dynamically if is_composite(comp_def) variables(comp_def) # make sure values have been gathered end try return comp_def.variables[var_name] - catch + catch KeyError error("Variable $var_name was not found in component $(comp_def.comp_id)") end end @@ -517,6 +565,7 @@ variable(dr::DatumReference) = variable(compdef(dr.comp_id), nameof(dr)) Return a list of all variable names for a given component `comp_name` in a model def `md`. """ +# TBD: why isn't this a @method of ComponentDef? variable_names(md::ModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) variable_names(comp_def::ComponentDef) = [nameof(var) for var in variables(comp_def)] @@ -595,6 +644,7 @@ end @method function comps_dict!(obj::CompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) obj.comps_dict = comps + dirty!(obj) end """ @@ -629,15 +679,13 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be end # Check if component being added already exists - if hascomp(obj, comp_name) + if has_comp(obj, comp_name) error("Cannot add two components of the same name ($comp_name)") end - # Create a deepcopy of the original but with the new name so - # it has separate variables and parameters, etc. - if compname(comp_def.comp_id) != comp_name - comp_def = copy_comp_def(comp_def, comp_name) - end + # Copy the original so we don't step on other uses of this comp + comp_def = deepcopy(comp_def) + comp_def.name = comp_name set_run_period!(comp_def, first, last) @@ -649,7 +697,7 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be new_comps = OrderedDict{Symbol, AbstractComponentDef}() if before !== nothing - if ! hascomp(obj, before) + if ! has_comp(obj, before) error("Component to add before ($before) does not exist") end @@ -661,7 +709,7 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be end else # after !== nothing, since we've handled all other possibilities above - if ! hascomp(obj, after) + if ! has_comp(obj, after) error("Component to add before ($before) does not exist") end @@ -684,7 +732,12 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be end end - return nothing + comp_path!(obj, comp_def) + + dirty!(obj) + + # Return the comp since it's a copy of what was passed in + return comp_def end """ @@ -695,7 +748,8 @@ Add the component indicated by `comp_id` to the composite component indicated by is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -@method function add_comp!(obj::CompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; +@method function add_comp!(obj::CompositeComponentDef, comp_id::ComponentId, + comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") @@ -715,12 +769,13 @@ added with the same first and last values, unless the keywords `first` or `last` Optional boolean argument `reconnect` with default value `true` indicates whether the existing parameter connections should be maintained in the new component. """ -@method function replace_comp!(obj::CompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; +@method function replace_comp!(obj::CompositeComponentDef, comp_id::ComponentId, + comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) - if ! hascomp(obj, comp_name) + if ! has_comp(obj, comp_name) error("Cannot replace '$comp_name'; component not found in model.") end @@ -741,7 +796,7 @@ parameter connections should be maintained in the new component. # Get original first and last if new run period not specified old_comp = compdef(obj, comp_name) first = first === nothing ? old_comp.first : first - last = last === nothing ? old_comp.last : last + last = last === nothing ? old_comp.last : last if reconnect # Assert that new component definition has same parameters and variables needed for the connections @@ -801,13 +856,16 @@ parameter connections should be maintained in the new component. add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end -""" - copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) +function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) + if isempty(path) + return obj + end -Copy the given `comp_def`, naming the copy `comp_name`. -""" -function copy_comp_def(comp_def::ComponentDef, comp_name::Symbol) - obj = deepcopy(comp_def) - obj.name = comp_name - return obj + name = path[1] + if has_comp(obj, name) + return find_comp(compdef(obj, name), path[2:end]) + end + return nothing end + +find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) diff --git a/src/core/delegate.jl b/src/core/delegate.jl index d67bb2a50..81ee8eaad 100644 --- a/src/core/delegate.jl +++ b/src/core/delegate.jl @@ -26,13 +26,14 @@ Macro to define a method that simply delegate to a method with the same signatur but using the specified field name of the original first argument as the first arg in the delegated call. That is, - `@delegate compid(ci::MetaComponentInstance, i::Int, f::Float64) => leaf` + `@delegate compid(ci::CompositeComponentInstance, i::Int, f::Float64) => leaf` expands to: - `compid(ci::MetaComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` + `compid(ci::CompositeComponentInstance, i::Int, f::Float64) = compid(ci.leaf, i, f)` -If a second expression is given, it is spliced in (basically to support "decache(m)") +If a second expression is given, it is spliced in, mainly to support the deprecated +decache(m)". We might delete this feature, but why bother? """ macro delegate(ex, other=nothing) result = nothing diff --git a/src/core/instances.jl b/src/core/instances.jl index 07fc00d57..aae11b23d 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -75,6 +75,8 @@ end end end +@method comp_paths(obj::ComponentInstanceData) = getfield(obj, :comp_paths) + """ get_param_value(ci::ComponentInstance, name::Symbol) diff --git a/src/core/model.jl b/src/core/model.jl index 9ca555ddd..edd4521a5 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -14,7 +14,7 @@ modeldef(m::Model) = m.md modelinstance(m::Model) = m.mi modelinstance_def(m::Model) = modeldef(modelinstance(m)) -is_built(m::Model) = (modelinstance(m) !== nothing) +is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate compinstance(m::Model, name::Symbol) => mi @delegate has_comp(m::Model, name::Symbol) => mi @@ -32,12 +32,6 @@ is_built(m::Model) = (modelinstance(m) !== nothing) @delegate add_connector_comps(m::Model) => md -# Forget any previously built model instance (i.e., after changing the model def). -# This should be called by all functions that modify the Model's underlying ModelDef. -function decache(m::Model) - m.mi = nothing -end - """ connect_param!(m::Model, dst_comp_name::Symbol, dst_par_name::Symbol, src_comp_name::Symbol, src_var_name::Symbol, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) @@ -74,22 +68,21 @@ function connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, S connect_param!(m.md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end -@delegate(disconnect_param!(m::Model, comp_name::Symbol, param_name::Symbol) => md, decache(m)) +@delegate disconnect_param!(m::Model, comp_name::Symbol, param_name::Symbol) => md -@delegate(set_external_param!(m::Model, name::Symbol, value::ModelParameter) => md, decache(m)) +@delegate set_external_param!(m::Model, name::Symbol, value::ModelParameter) => md -@delegate(set_external_param!(m::Model, name::Symbol, value::Number; - param_dims::Union{Nothing,Array{Symbol}} = nothing) => md, decache(m)) +@delegate set_external_param!(m::Model, name::Symbol, value::Number; + param_dims::Union{Nothing,Array{Symbol}} = nothing) => md -@delegate(set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; - param_dims::Union{Nothing,Array{Symbol}} = nothing) => md, decache(m)) +@delegate set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; + param_dims::Union{Nothing,Array{Symbol}} = nothing) => md -@delegate(add_internal_param_conn!(m::Model, conn::InternalParameterConnection) => md, decache(m)) +@delegate add_internal_param_conn!(m::Model, conn::InternalParameterConnection) => md # @delegate doesn't handle the 'where T' currently. This is the only instance of it for now... function set_leftover_params!(m::Model, parameters::Dict{T, Any}) where T set_leftover_params!(m.md, parameters) - decache(m) end """ @@ -100,7 +93,7 @@ Update the `value` of an external model parameter in model `m`, referenced by indicates whether to update the time keys associated with the parameter values to match the model's time index. """ -@delegate(update_param!(m::Model, name::Symbol, value; update_timesteps = false) => md, decache(m)) +@delegate update_param!(m::Model, name::Symbol, value; update_timesteps = false) => md """ update_params!(m::Model, parameters::Dict{T, Any}; update_timesteps = false) where T @@ -111,7 +104,7 @@ Boolean argument update_timesteps. Each key k must be a symbol or convert to a symbol matching the name of an external parameter that already exists in the model definition. """ -@delegate(update_params!(m::Model, parameters::Dict; update_timesteps = false) => md, decache(m)) +@delegate update_params!(m::Model, parameters::Dict; update_timesteps = false) => md """ add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; @@ -124,7 +117,6 @@ differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first=nothing, last=nothing, before=nothing, after=nothing) add_comp!(m.md, comp_id, comp_name; first=first, last=last, before=before, after=after) - decache(m) return ComponentReference(m, comp_name) end @@ -147,7 +139,6 @@ function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) replace_comp!(m.md, comp_id, comp_name; first=first, last=last, before=before, after=after, reconnect=reconnect) - decache(m) return ComponentReference(m, comp_name) end @@ -208,9 +199,9 @@ dim_names(m::Model, comp_name::Symbol, datum_name::Symbol) = dim_names(compdef(m Set the values of `m` dimension `name` to integers 1 through `count`, if `keys`` is an integer; or to the values in the vector or range if `keys`` is either of those types. """ -@delegate(set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => md, decache(m)) +@delegate set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => md -@delegate(set_run_period!(m::Model, first, last) => md, decache(m)) +@delegate set_run_period!(m::Model, first, last) => md @delegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md @@ -231,10 +222,7 @@ Return a list of the parameter definitions for `comp_name` in model `m`. """ parameters(m::Model, comp_name::Symbol) = parameters(compdef(m, comp_name)) -function variable(m::Model, comp_name::Symbol, var_name::Symbol) - vars = variables(m, comp_name) - return vars[var_name] -end +variable(m::Model, comp_name::Symbol, var_name::Symbol) = variable(compdef(m, comp_name), var_name) function variable_unit(m::Model, comp_name::Symbol, var_name::Symbol) var = variable(m, comp_id, var_name) @@ -249,7 +237,7 @@ end """ variables(m::Model, comp_name::Symbol) -Return a list of the variable definitions for `comp_name` in model `m`. +Return an iterator on the variable definitions for `comp_name` in model `m`. """ variables(m::Model, comp_name::Symbol) = variables(compdef(m, comp_name)) @@ -261,21 +249,21 @@ variables(m::Model, comp_name::Symbol) = variables(compdef(m, comp_name)) Add a one or two dimensional (optionally, time-indexed) array parameter `name` with value `value` to the model `m`. """ -@delegate(set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) => md, decache(m)) +@delegate set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) => md """ set_external_scalar_param!(m::Model, name::Symbol, value::Any) Add a scalar type parameter `name` with value `value` to the model `m`. """ -@delegate(set_external_scalar_param!(m::Model, name::Symbol, value::Any) => md, decache(m)) +@delegate set_external_scalar_param!(m::Model, name::Symbol, value::Any) => md """ - delete!(m::ModelDef, component::Symbol + delete!(m::Model, component::Symbol Delete a `component`` by name from a model `m`'s ModelDef, and nullify the ModelInstance. """ -@delegate(Base.delete!(m::Model, comp_name::Symbol) => md, decache(m)) +@delegate Base.delete!(m::Model, comp_name::Symbol) => md """ set_param!(m::Model, comp_name::Symbol, name::Symbol, value, dims=nothing) @@ -285,20 +273,20 @@ The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ -@delegate(set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md, decache(m)) +@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md """ run(m::Model) Run model `m` once. """ -function Base.run(m::Model; ntimesteps::Int=typemax(Int), +function Base.run(m::Model; ntimesteps::Int=typemax(Int), rebuild::Bool=false, dim_keys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) if numcomponents(m) == 0 error("Cannot run a model with no components.") end - if ! is_built(m) + if (rebuild || ! is_built(m)) build(m) end diff --git a/src/utils/show.jl b/src/core/show.jl similarity index 85% rename from src/utils/show.jl rename to src/core/show.jl index 9db9b8a03..baf6b8368 100644 --- a/src/utils/show.jl +++ b/src/core/show.jl @@ -13,8 +13,7 @@ function _indent_level!(io::IO, delta::Int) return IOContext(io, :indent_level => max(level + delta, 0)) end -indent(io::IO) = _indent_level!(io, 1) -outdent(io::IO) = _indent_level!(io, -1) +indent(io::IO) = _indent_level!(io, 1) function print_indented(io::IO, args...) level = get(io, :indent_level, 0) @@ -22,16 +21,6 @@ function print_indented(io::IO, args...) nothing end -function show(io::IO, obj::ComponentId) - print(io, "") - nothing -end - -function show(io::IO, obj::AbstractDimension) - print(io, keys(obj)) - nothing -end - function _show_field(io::IO, name::Symbol, value; show_empty=true) if !show_empty && isempty(value) return @@ -60,19 +49,20 @@ end function _show_field(io::IO, name::Symbol, vec::Vector{T}; show_empty=true) where T count = length(vec) - ellipsis = false + elide = false max_shown = 5 if count > max_shown last = vec[end] vec = vec[1:max_shown-1] - ellipsis = true + elide = true end - io = indent(io) + for (i, value) in enumerate(vec) print(io, "\n") print_indented(io, "$i: ", value) end - if ellipsis + + if elide print(io, "\n") print_indented(io, "...\n") print_indented(io, "$count: ") @@ -98,6 +88,15 @@ function _show_datum_def(io::IO, obj::AbstractDatumDef) for field in (:description, :unit) _show_field(io, field, getfield(obj, field), show_empty=false) end +end + +function show(io::IO, obj::ComponentId) + print(io, "") + nothing +end + +function show(io::IO, obj::AbstractDimension) + print(io, keys(obj)) nothing end @@ -120,5 +119,16 @@ function show(io::IO, obj::AbstractMimiType) fields = deleteat!([fields...], pos) end _show_fields(indent(io), obj, fields) - nothing end + +function show(io::IO, obj::ModelInstance) + # Don't print full type signature since it's shown in .variables and .parameters + print(io, "ModelInstance") + + # Don't print the mi's ModelDef since it's redundant + fields = fieldnames(typeof(obj)) + pos = findfirst(x -> x == :md, fields) + fields = deleteat!([fields...], pos) + + _show_fields(indent(io), obj, fields) +end \ No newline at end of file diff --git a/src/core/types.jl b/src/core/types.jl index a3c70a651..191c6cd76 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -176,14 +176,22 @@ end # 4. Types supporting structural definition of models and their components # -# To identify components, we create a variable with the name of the component -# whose value is an instance of this type, e.g. -# const global adder = ComponentId(module_name, comp_name) +# To identify components, @defcomp creates a variable with the name of +# the component whose value is an instance of this type. struct ComponentId <: MimiStruct module_name::Symbol comp_name::Symbol end +# Identifies the path through multiple composites to a leaf component +# TBD: Could be just a tuple of Symbols since they are unique at each level. +const ComponentPath = NTuple{N, Symbol} where N + +ComponentPath(names::Vector{Symbol}) = Tuple(names) + +# The equivalent of ".." in the file system. +Base.parent(path::ComponentPath) = path[1:end-1] + ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) # @@ -205,21 +213,12 @@ Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, @method Base.nameof(obj::NamedObj) = obj.name # Stores references to the name of a component variable or parameter +# and the ComponentId of the component in which it is defined @class DatumReference <: NamedObj begin - comp_id::ComponentId + # TBD: should be a ComponentPath + comp_id::ComponentId # TBD: should this be a ComponentPath? end -comp_name(dr::DatumReference) = dr.comp_id.comp_name - -# *Def implementation doesn't need to be performance-optimized since these -# are used only to create *Instance objects that are used at run-time. With -# this in mind, we don't create dictionaries of vars, params, or dims in the -# ComponentDef since this would complicate matters if a user decides to -# add/modify/remove a component. Instead of maintaining a secondary dict, -# we just iterate over sub-components at run-time as needed. - -global const BindingTypes = Union{Int, Float64, DatumReference} - # Similar structure is used for variables and parameters (parameters merely adds `default`) @class mutable DatumDef <: NamedObj begin datatype::DataType @@ -237,6 +236,7 @@ end @class mutable ComponentDef <: NamedObj begin comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) + comp_path::Union{Nothing, ComponentPath} variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} @@ -252,19 +252,18 @@ end function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; name::Union{Nothing, Symbol}=nothing) if name === nothing - name = (comp_id === nothing ? gensym(:anonymous) : comp_id.comp_name) + name = (comp_id === nothing ? gensym(nameof(typeof(self))) : comp_id.comp_name) end NamedObj(self, name) self.comp_id = comp_id + self.comp_path = nothing # this is set in add_comp!() self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.first = self.last = nothing self.is_uniform = true return self - - return ComponentDef(comp_id, name=name) end function ComponentDef(comp_id::Union{Nothing, ComponentId}; @@ -280,10 +279,15 @@ end @method last_period(obj::ComponentDef) = obj.last @method isuniform(obj::ComponentDef) = obj.is_uniform +# Define type aliases to avoid repeating these in several places +global const BindingsDef = Vector{Pair{T where T <: AbstractDatumReference, Union{Int, Float64, DatumReference}}} +global const ExportsDef = Dict{Symbol, AbstractDatumReference} + @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} - bindings::Vector{Pair{DatumReference, BindingTypes}} - exports::Vector{Pair{DatumReference, Symbol}} + bindings::BindingsDef + + exports::ExportsDef internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} @@ -297,9 +301,10 @@ end function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::ComponentId, comps::Vector{<: AbstractComponentDef}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) + bindings::BindingsDef, + exports::ExportsDef) + # TBD: OrderedDict{ComponentId, AbstractComponentDef} comps_dict = OrderedDict{Symbol, AbstractComponentDef}([nameof(cd) => cd for cd in comps]) in_conns = Vector{InternalParameterConnection}() ex_conns = Vector{ExternalParameterConnection}() @@ -314,8 +319,8 @@ end end function CompositeComponentDef(comp_id::ComponentId, comps::Vector{<: AbstractComponentDef}, - bindings::Vector{Pair{DatumReference, BindingTypes}}, - exports::Vector{Pair{DatumReference, Symbol}}) + bindings::BindingsDef, + exports::ExportsDef) self = new() return CompositeComponentDef(self, comp_id, comps, bindings, exports) @@ -325,20 +330,24 @@ end function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) self = (self === nothing ? new() : self) - comp_id = ComponentId(@__MODULE__, gensym(:anonymous)) + comp_id = ComponentId(@__MODULE__, gensym(nameof(typeof(self)))) comps = Vector{T where T <: AbstractComponentDef}() - bindings = Vector{Pair{DatumReference, BindingTypes}}() - exports = Vector{Pair{DatumReference, Symbol}}() + bindings = BindingsDef() + exports = ExportsDef() return CompositeComponentDef(self, comp_id, comps, bindings, exports) end end +# TBD: these should dynamically and recursively compute the lists @method internal_param_conns(obj::CompositeComponentDef) = obj.internal_param_conns @method external_param_conns(obj::CompositeComponentDef) = obj.external_param_conns @method external_params(obj::CompositeComponentDef) = obj.external_params @method external_param(obj::CompositeComponentDef, name::Symbol) = obj.external_params[name] +@method exported_names(obj::CompositeComponentDef) = keys(obj.exports) +@method is_exported(obj::CompositeComponentDef, name::Symbol) = haskey(obj.exports, name) + @method add_backup!(obj::CompositeComponentDef, backup) = push!(obj.backups, backup) @method is_leaf(c::ComponentDef) = true @@ -347,11 +356,13 @@ end @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType + dirty::Bool function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - return ModelDef(self, number_type) + self.comp_path = (self.name,) + return ModelDef(self, number_type, false) end end @@ -362,6 +373,7 @@ end # Supertype for variables and parameters in component instances @class ComponentInstanceData{NT <: NamedTuple} <: MimiClass begin nt::NT + comp_paths::Vector{ComponentPath} # records the origin of each datum end @method nt(obj::ComponentInstanceData) = getfield(obj, :nt) @@ -369,17 +381,37 @@ end @method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) @method Base.values(obj::ComponentInstanceData) = values(nt(obj)) -@class ComponentInstanceParameters <: ComponentInstanceData -@class ComponentInstanceVariables <: ComponentInstanceData - -function ComponentInstanceParameters(names, types, values) - NT = NamedTuple{names, types} - return ComponentInstanceParameters{NT}(NT(values)) +# Centralizes the shared functionality from the two component data subtypes. +function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, + names, types, values, paths) + NT = NamedTuple{Tuple(names), Tuple{types...}} + return subtype(NT(values), Vector{ComponentPath}(paths)) end -function ComponentInstanceVariables(names, types, values) - NT = NamedTuple{names, types} - return ComponentInstanceVariables{NT}(NT(values)) +@class ComponentInstanceParameters <: ComponentInstanceData begin + function ComponentInstanceParameters(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} + return new{NT}(nt, paths) + end + + function ComponentInstanceParameters(names::Vector{Symbol}, + types::Vector{DataType}, + values::Vector{Any}, + paths) + return _datum_instance(ComponentInstanceParameters, names, types, values, paths) + end +end + +@class ComponentInstanceVariables <: ComponentInstanceData begin + function ComponentInstanceVariables(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} + return new{NT}(nt, paths) + end + + function ComponentInstanceVariables(names::Vector{Symbol}, + types::Vector{DataType}, + values::Vector{Any}, + paths) + return _datum_instance(ComponentInstanceVariables, names, types, values, paths) + end end # A container class that wraps the dimension dictionary when passed to run_timestep() @@ -401,6 +433,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro @class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: MimiClass begin comp_name::Symbol comp_id::ComponentId + comp_path::ComponentPath variables::TV parameters::TP first::Union{Nothing, Int} @@ -416,6 +449,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} self.comp_id = comp_id = comp_def.comp_id + self.comp_path = comp_def.comp_path self.comp_name = name self.variables = vars self.parameters = pars @@ -435,7 +469,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro return nothing end - func_name = Symbol("$(name)_$(self.comp_name)") + func_name = Symbol("$(name)_$(self.comp_id.comp_name)") try Base.eval(comp_module, func_name) catch err @@ -523,12 +557,15 @@ Create a single ComponentInstanceParameters type reflecting those of a composite component's parameters, and similarly for its variables. """ function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) - vtypes = [] - vnames = [] + vtypes = DataType[] + vnames = Symbol[] vvalues = [] - ptypes = [] - pnames = [] + vpaths = [] + + ptypes = DataType[] + pnames = Symbol[] pvalues = [] + ppaths = [] for comp in comps v = comp.variables @@ -542,10 +579,13 @@ function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) append!(vvalues, values(v)) append!(pvalues, values(p)) + + append!(vpaths, comp_paths(v)) + append!(ppaths, comp_paths(p)) end - vars = ComponentInstanceVariables(Tuple(vnames), Tuple{vtypes...}, vvalues) - pars = ComponentInstanceParameters(Tuple(pnames), Tuple{ptypes...}, pvalues) + vars = ComponentInstanceVariables(vnames, vtypes, vvalues, vpaths) + pars = ComponentInstanceParameters(pnames, ptypes, pvalues, ppaths) return vars, pars end diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 66ebda839..e236270f0 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -43,8 +43,6 @@ end y = Variable() # different variable dimensions end - - # 1. Test scenario where the replacement works m = Model() From 84798e96b34c167f71004378b3a2597876030afe Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 12 Feb 2019 14:23:54 -0800 Subject: [PATCH 33/81] - Removed deprecated @method macro - Continued debugging - Simplified some code by using app-defined functions - Created "registry" of active ModelDefs (using a weak key dict and finalizer) to implement find_comp() --- src/core/build.jl | 12 +- src/core/connections.jl | 28 ++-- src/core/defs.jl | 221 +++++++++++++++++-------- src/core/instances.jl | 32 ++-- src/core/model.jl | 16 +- src/core/show.jl | 27 ++- src/core/types.jl | 115 +++++++------ test/test_metainfo.jl | 12 +- test/test_metainfo_variabletimestep.jl | 3 - test/test_replace_comp.jl | 2 +- test/test_tmp.jl | 46 +++++ 11 files changed, 341 insertions(+), 173 deletions(-) create mode 100644 test/test_tmp.jl diff --git a/src/core/build.jl b/src/core/build.jl index f23a02f83..20b9bfbcc 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -74,7 +74,7 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) end # Create ComponentInstanceVariables for a composite component from the list of exported vars -@method function _combine_exported_vars(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}) +function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dict::Dict{Symbol, Any}) names = Symbol[] values = Any[] @@ -92,7 +92,7 @@ end return ComponentInstanceVariables(names, types, values, paths) end -@method function _combine_exported_pars(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) names = Symbol[] values = Any[] @@ -125,7 +125,7 @@ end # Recursively instantiate all variables and store refs in the given dict. -@method function _instantiate_vars(comp_def::CompositeComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) comp_name = nameof(comp_def) par_dict[comp_name] = Dict() @@ -141,7 +141,7 @@ end _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing # Recursively collect all parameters with connections to allocated storage for variables -@method function _collect_params(comp_def::CompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _collect_params(comp_def::AbstractCompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) # depth-first search of composites for cd in compdefs(comp_def) _collect_params(cd, var_dict, par_dict) @@ -189,7 +189,7 @@ function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict return ComponentInstanceParameters(names, types, vals, paths) end -@method function _instantiate_params(comp_def::CompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _instantiate_params(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) _combine_exported_pars(comp_def, par_dict) end @@ -209,7 +209,7 @@ function _build(comp_def::ComponentDef, return ComponentInstance(comp_def, vars, pars, time_bounds) end -@method function _build(comp_def::CompositeComponentDef, +function _build(comp_def::AbstractCompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}, time_bounds::Tuple{Int, Int}) diff --git a/src/core/connections.jl b/src/core/connections.jl index d4868250c..753f059de 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -70,7 +70,7 @@ function connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext comp_def = compdef(md, comp_name) ext_param = external_param(md, ext_param_name) - if isa(ext_param, ArrayModelParameter) + if ext_param isa ArrayModelParameter _check_labels(md, comp_def, param_name, ext_param) end @@ -233,12 +233,10 @@ the dictionary keys are strings that match the names of unset parameters in the """ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T parameters = Dict(k => v for (k, v) in parameters) - leftovers = unconnected_params(md) - ext_params = external_params(md) - for (comp_name, param_name) in leftovers + for (comp_name, param_name) in unconnected_params(md) # check whether we need to set the external parameter - if ! haskey(ext_params, param_name) + if external_param(md, param_name, missing_ok=true) !== nothing value = parameters[string(param_name)] param_dims = parameter_dimensions(md, comp_name, param_name) @@ -251,20 +249,22 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T end # Find internal param conns to a given destination component -@method function internal_param_conns(obj::CompositeComponentDef, dst_comp_name::Symbol) +function internal_param_conns(obj::AbstractCompositeComponentDef, dst_comp_name::Symbol) return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(obj)) end # Find external param conns for a given comp -@method function external_param_conns(obj::CompositeComponentDef, comp_name::Symbol) +function external_param_conns(obj::AbstractCompositeComponentDef, comp_name::Symbol) return filter(x -> x.comp_name == comp_name, external_param_conns(obj)) end -function external_param(obj::CompositeComponentDef, name::Symbol) +function external_param(obj::AbstractCompositeComponentDef, name::Symbol; missing_ok=false) try return obj.external_params[name] catch err if err isa KeyError + missing_ok && return nothing + error("$name not found in external parameter list") else rethrow(err) @@ -272,17 +272,17 @@ function external_param(obj::CompositeComponentDef, name::Symbol) end end -@method function add_internal_param_conn!(obj::CompositeComponentDef, conn::InternalParameterConnection) +function add_internal_param_conn!(obj::AbstractCompositeComponentDef, conn::InternalParameterConnection) push!(obj.internal_param_conns, conn) dirty!(obj) end -@method function add_external_param_conn!(obj::CompositeComponentDef, conn::ExternalParameterConnection) +function add_external_param_conn!(obj::AbstractCompositeComponentDef, conn::ExternalParameterConnection) push!(obj.external_param_conns, conn) dirty!(obj) end -@method function set_external_param!(obj::CompositeComponentDef, name::Symbol, value::ModelParameter) +function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::ModelParameter) obj.external_params[name] = value dirty!(obj) end @@ -365,13 +365,11 @@ function update_param!(md::ModelDef, name::Symbol, value; update_timesteps = fal end function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true) - ext_params = external_params(md) - if ! haskey(ext_params, name) + param = external_param(ext_params, name, missing_ok=true) + if param === nothing error("Cannot update parameter; $name not found in model's external parameters.") end - param = ext_params[name] - if param isa ScalarModelParameter if update_timesteps && raise_error error("Cannot update timesteps; parameter $name is a scalar parameter.") diff --git a/src/core/defs.jl b/src/core/defs.jl index 5f35b55cc..324684818 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -18,21 +18,23 @@ function compdef(comp_name::Symbol) end end -@delegate compdef(dr::DatumReference) => comp_id +@delegate compdef(dr::AbstractDatumReference) => comp_path + +compdef(path::ComponentPath) = find_comp(path) # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] -@method compdefs(c::CompositeComponentDef) = values(c.comps_dict) -@method compkeys(c::CompositeComponentDef) = keys(c.comps_dict) -@method has_comp(c::CompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) -@method compdef(c::CompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] +compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) +compkeys(c::AbstractCompositeComponentDef) = keys(c.comps_dict) +has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) +compdef(c::AbstractCompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name -@method compmodule(obj::ComponentDef) = compmodule(obj.comp_id) -@method compname(obj::ComponentDef) = compname(obj.comp_id) +compmodule(obj::AbstractComponentDef) = compmodule(obj.comp_id) +compname(obj::AbstractComponentDef) = compname(obj.comp_id) compnames() = map(compname, compdefs()) @@ -45,16 +47,13 @@ function reset_compdefs(reload_builtins=true) end end -_append_path(path::Union{Nothing, ComponentPath}, name::Symbol) = (path === nothing ? (name,) : (path..., name)) - -@method function comp_path!(parent::CompositeComponentDef, child::AbstractComponentDef) - child.comp_path = _append_path(parent.comp_path, child.name) - # @info "Setting comp path to $(child.comp_path)" +function comp_path!(parent::AbstractCompositeComponentDef, child::AbstractComponentDef) + child.comp_path = ComponentPath(parent.comp_path, child.name) end dirty(md::ModelDef) = md.dirty -@method function dirty!(obj::ComponentDef) +function dirty!(obj::AbstractComponentDef) path = obj.comp_path if (path === nothing || isempty(path)) return @@ -70,7 +69,7 @@ end dirty!(md::ModelDef) = (md.dirty = true) -@method function Base.parent(obj::ComponentDef) +function Base.parent(obj::AbstractComponentDef) parent_path = parent(obj.comp_path) return compdef(parent_path) end @@ -98,17 +97,20 @@ function last_period(md::ModelDef, comp_def::AbstractComponentDef) return period === nothing ? time_labels(md)[end] : period end -@delegate compname(dr::DatumReference) => comp_id -@delegate compmodule(dr::DatumReference) => comp_id +compname(dr::AbstractDatumReference) = dr.comp_path.names[end] +#@delegate compmodule(dr::DatumReference) => comp_id + +is_variable(dr::AbstractDatumReference) = false +is_parameter(dr::AbstractDatumReference) = false -is_variable(dr::DatumReference) = has_variable(compdef(dr.comp_id), nameof(dr)) -is_parameter(dr::DatumReference) = has_parameter(compdef(dr.comp_id), nameof(dr)) +is_variable(dr::VariableDefReference) = has_variable(compdef(dr), nameof(dr)) +is_parameter(dr::ParameterDefReference) = has_parameter(compdef(dr), nameof(dr)) number_type(md::ModelDef) = md.number_type # TBD: should be numcomps() -@method numcomponents(obj::ComponentDef) = 0 # no sub-components -@method numcomponents(obj::CompositeComponentDef) = length(obj.comps_dict) +numcomponents(obj::AbstractComponentDef) = 0 # no sub-components +numcomponents(obj::AbstractCompositeComponentDef) = length(obj.comps_dict) """ new_comp(comp_id::ComponentId, verbose::Bool=true) @@ -132,17 +134,32 @@ function new_comp(comp_id::ComponentId, verbose::Bool=true) end """ - delete!(obj::CompositeComponentDef, component::Symbol) + delete!(obj::AbstractCompositeComponentDef, component::Symbol) Delete a `component` by name from a model definition `m`. """ -@method function Base.delete!(ccd::CompositeComponentDef, comp_name::Symbol) +function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) if ! has_comp(ccd, comp_name) error("Cannot delete '$comp_name': component does not exist.") end + comp_def = compdef(ccd, comp_name) delete!(ccd.comps_dict, comp_name) + + # Remove references to the deleted comp + comp_path = comp_def.comp_path + exports = ccd.exports + + for (key, dr) in exports + if dr.comp_path == comp_path + delete!(exports, key) + end + end + + # TBD: find and delete external_params associated with deleted component? Currently no record of this. + + # TBD: these probably need to use comp_path rather than symbols ipc_filter = x -> x.src_comp_name != comp_name && x.dst_comp_name != comp_name filter!(ipc_filter, ccd.internal_param_conns) @@ -154,7 +171,7 @@ end # Dimensions # -@method function add_dimension!(comp::ComponentDef, name) +function add_dimension!(comp::AbstractComponentDef, name) # generally, we add dimension name with nothing instead of a Dimension instance, # but in the case of an Int name, we create the "anonymous" dimension on the fly. dim = (name isa Int) ? Dimension(name) : nothing @@ -164,7 +181,7 @@ end # Note that this operates on the registered comp, not one added to a composite add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) -@method function dim_names(ccd::CompositeComponentDef) +function dim_names(ccd::AbstractCompositeComponentDef) dims = OrderedSet{Symbol}() # use a set to eliminate duplicates for cd in compdefs(ccd) union!(dims, keys(dim_dict(cd))) # TBD: test this @@ -173,16 +190,16 @@ add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), na return collect(dims) end -@method dim_names(comp_def::ComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) +dim_names(comp_def::AbstractComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) -@method dim_count(def::DatumDef) = length(dim_names(def)) +dim_count(def::AbstractDatumDef) = length(dim_names(def)) function step_size(values::Vector{Int}) return length(values) > 1 ? values[2] - values[1] : 1 end # -# TBD: should these be defined as @method of CompositeComponentDef +# TBD: should these be defined as methods of CompositeComponentDef? # function step_size(md::ModelDef) keys::Vector{Int} = time_labels(md) @@ -198,7 +215,7 @@ function first_and_step(values::Vector{Int}) return values[1], step_size(values) end -@method first_and_last(obj::ComponentDef) = (obj.first, obj.last) +first_and_last(obj::AbstractComponentDef) = (obj.first, obj.last) function time_labels(md::ModelDef) keys::Vector{Int} = dim_keys(md, :time) @@ -237,13 +254,13 @@ function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) end # Symbols are added to the dim_dict in @defcomp (with value of nothing), but are set later using set_dimension! -@method has_dim(obj::CompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) +has_dim(obj::AbstractCompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) -@method isuniform(obj::CompositeComponentDef) = obj.is_uniform +isuniform(obj::AbstractCompositeComponentDef) = obj.is_uniform -@method set_uniform!(obj::CompositeComponentDef, value::Bool) = (obj.is_uniform = value) +set_uniform!(obj::AbstractCompositeComponentDef, value::Bool) = (obj.is_uniform = value) -@method dimension(obj::CompositeComponentDef, name::Symbol) = obj.dim_dict[name] +dimension(obj::AbstractCompositeComponentDef, name::Symbol) = obj.dim_dict[name] dim_names(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] @@ -255,7 +272,7 @@ dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) dim_values(md::ModelDef, name::Symbol) = collect(values(dimension(md, name))) # For debugging only -@method function _show_run_period(obj::ComponentDef, first, last) +function _show_run_period(obj::AbstractComponentDef, first, last) first = (first === nothing ? :nothing : first) last = (last === nothing ? :nothing : last) which = (is_leaf(obj) ? :leaf : :composite) @@ -271,7 +288,7 @@ If the component has an earlier start than `first` or a later finish than `last` the values are reset to the tighter bounds. Values of `nothing` are left unchanged. Composites recurse on sub-components. """ -@method function set_run_period!(obj::ComponentDef, first, last) +function set_run_period!(obj::AbstractComponentDef, first, last) #_show_run_period(obj, first, last) first_per = first_period(obj) last_per = last_period(obj) @@ -307,7 +324,7 @@ end Set the values of `md` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. """ -@method function set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) +function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) redefined = has_dim(ccd, name) if redefined @warn "Redefining dimension :$name" @@ -321,7 +338,7 @@ an integer; or to the values in the vector or range if `keys` is either of those return set_dimension!(ccd, name, Dimension(keys)) end -@method function set_dimension!(obj::CompositeComponentDef, name::Symbol, dim::Dimension) +function set_dimension!(obj::AbstractCompositeComponentDef, name::Symbol, dim::Dimension) dirty!(obj) obj.dim_dict[name] = dim end @@ -351,9 +368,9 @@ end # # Callable on both ParameterDef and VariableDef -@method dim_names(obj::DatumDef) = obj.dim_names +dim_names(obj::AbstractDatumDef) = obj.dim_names -@method function addparameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) +function addparameter(comp_def::AbstractComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, datatype, dimensions, description, unit, default) comp_def.parameters[name] = p dirty!(comp_def) @@ -369,9 +386,9 @@ end Return a list of the parameter definitions for `comp_def`. """ -@method parameters(obj::ComponentDef) = values(obj.parameters) +parameters(obj::AbstractComponentDef) = values(obj.parameters) -@method function parameters(ccd::CompositeComponentDef) +function parameters(ccd::AbstractCompositeComponentDef) pars = ccd.parameters # return cached parameters, if any @@ -394,7 +411,8 @@ Return a list of the parameter definitions for `comp_id`. """ parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) -@method parameters(obj::DatumReference) = parameters(obj.comp_id) +# TBD: deprecated? +# parameters(obj::ParameterDefReference) = parameters(obj.comp_id) """ parameter_names(md::ModelDef, comp_name::Symbol) @@ -404,13 +422,13 @@ Return a list of all parameter names for a given component `comp_name` in a mode parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) #parameter_names(comp_def::ComponentDef) = [nameof(param) for param in parameters(comp_def)] -@method parameter_names(comp_def::ComponentDef) = collect(keys(comp_def.parameters)) +parameter_names(comp_def::AbstractComponentDef) = collect(keys(comp_def.parameters)) -@method parameter(obj::CompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) +parameter(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) -@method parameter(dr::DatumReference) = parameter(compdef(dr.comp_id), nameof(dr)) +parameter(dr::ParameterDefReference) = parameter(compdef(dr), nameof(dr)) -@method function _parameter(obj::ComponentDef, name::Symbol) +function _parameter(obj::AbstractComponentDef, name::Symbol) try return obj.parameters[name] catch @@ -422,7 +440,7 @@ function parameter(obj::ComponentDef, name::Symbol) _parameter(obj, name) end -@method function parameter(obj::CompositeComponentDef, name::Symbol) +function parameter(obj::AbstractCompositeComponentDef, name::Symbol) if ! is_exported(obj, name) error("Parameter $name is not exported by composite component $(obj.comp_path)") end @@ -430,14 +448,14 @@ end end -@method has_parameter(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.parameters, name) +has_parameter(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.parameters, name) -@method function parameter_unit(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) +function parameter_unit(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) param = parameter(obj, comp_name, param_name) return param.unit end -@method function parameter_dimensions(obj::ComponentDef, comp_name::Symbol, param_name::Symbol) +function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) param = parameter(obj, comp_name, param_name) return dim_names(param) end @@ -514,10 +532,10 @@ end # # Variables # -@method variables(comp_def::ComponentDef) = values(comp_def.variables) +variables(comp_def::AbstractComponentDef) = values(comp_def.variables) # TBD: if we maintain vars/pars dynamically, this can be dropped -@method function variables(ccd::CompositeComponentDef) +function variables(ccd::AbstractCompositeComponentDef) vars = ccd.variables # return cached variables, if any @@ -535,11 +553,12 @@ end variables(comp_id::ComponentId) = variables(compdef(comp_id)) -variables(dr::DatumReference) = variables(dr.comp_id) +# TBD: Not sure this makes sense +# variables(dr::DatumReference) = variables(dr.comp_id) # TBD: Perhaps define _variable to behave as below, and have the public version # check it's exported before returning it. (Could error("exists but not exported?")) -@method function variable(comp_def::ComponentDef, var_name::Symbol) +function variable(comp_def::AbstractComponentDef, var_name::Symbol) # TBD test this can be dropped if we maintain vars/pars dynamically if is_composite(comp_def) variables(comp_def) # make sure values have been gathered @@ -556,17 +575,17 @@ variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), va variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(md, comp_name), var_name) -variable(dr::DatumReference) = variable(compdef(dr.comp_id), nameof(dr)) +variable(obj::VariableDefReference) = variable(compdef(obj), nameof(dr)) -@method has_variable(comp_def::ComponentDef, name::Symbol) = haskey(comp_def.variables, name) +has_variable(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.variables, name) """ variable_names(md::ModelDef, comp_name::Symbol) Return a list of all variable names for a given component `comp_name` in a model def `md`. """ -# TBD: why isn't this a @method of ComponentDef? -variable_names(md::ModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) +# TBD: why isn't this a of ComponentDef? +variable_names(md::AbstractModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) variable_names(comp_def::ComponentDef) = [nameof(var) for var in variables(comp_def)] @@ -590,11 +609,11 @@ function addvariable(comp_def::ComponentDef, name, datatype, dimensions, descrip end """ - addvariables(obj::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) + addvariables(obj::CompositeComponentDef, exports::Vector{Pair{AbstractDatumReference, Symbol}}) Add all exported variables to a CompositeComponentDef. """ -@method function addvariables(obj::CompositeComponentDef, exports::Vector{Pair{DatumReference, Symbol}}) +function addvariables(obj::AbstractCompositeComponentDef, exports::Vector{Pair{AbstractDatumReference, Symbol}}) # TBD: this needs attention for (dr, exp_name) in exports addvariable(obj, variable(obj, nameof(variable)), exp_name) @@ -611,12 +630,12 @@ end # # Return the number of timesteps a given component in a model will run for. -@method function getspan(obj::CompositeComponentDef, comp_name::Symbol) +function getspan(obj::AbstractCompositeComponentDef, comp_name::Symbol) comp_def = compdef(obj, comp_name) return getspan(obj, comp_def) end -@method function getspan(obj::CompositeComponentDef, comp_def::ComponentDef) +function getspan(obj::AbstractCompositeComponentDef, comp_def::ComponentDef) first = first_period(obj, comp_def) last = last_period(obj, comp_def) times = time_labels(obj) @@ -628,10 +647,8 @@ end # # Model # -const NothingInt = Union{Nothing, Int} -const NothingSymbol = Union{Nothing, Symbol} -@method function _append_comp!(obj::CompositeComponentDef, comp_name::Symbol, comp_def::AbstractComponentDef) +function _append_comp!(obj::AbstractCompositeComponentDef, comp_name::Symbol, comp_def::AbstractComponentDef) obj.comps_dict[comp_name] = comp_def end @@ -642,21 +659,62 @@ function _add_anonymous_dims!(md::ModelDef, comp_def::AbstractComponentDef) end end -@method function comps_dict!(obj::CompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) +function comps_dict!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) obj.comps_dict = comps dirty!(obj) end +function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, name::Symbol) + path = ComponentPath(parent.comp_path, comp_def.name) + + if has_variable(comp_def, name) + return VariableDefReference(name, path) + end + + if has_parameter(comp_def, name) + return ParameterDefReference(name, path) + end + + error("$(comp_def.comp_path) does not have a data item named $name") +end + +const NothingInt = Union{Nothing, Int} +const NothingSymbol = Union{Nothing, Symbol} +const ExportList = Vector{Union{Symbol, Pair{Symbol, Symbol}}} + """ - add_comp!(md::ModelDef, comp_def::ComponentDef; first=nothing, last=nothing, before=nothing, after=nothing) + add_comp!(md::ModelDef, comp_def::ComponentDef; + exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. +The `exports` arg identifies which vars/pars to export and with what names. If `nothing`, everything is +exported. The first element of a pair indicates the symbol to export from comp_def to the composite, +the second element allows this var/par to have a new name in the composite. A symbol alone means to use +the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] """ -@method function add_comp!(obj::CompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; +function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; + exports=nothing, first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) + + # if not specified, export all var/pars. Caller can pass empty list to export nothing. + if exports === nothing + exports = [variable_names(comp_def)..., parameter_names(comp_def)...] + end + + for item in exports + if item isa Pair + (name, export_name) = item + elseif item isa Symbol + name = export_name = item + else + error("Exports argument to add_comp! must be pair or symbol, got: $item") + end + + obj.exports[export_name] = _find_var_par(obj, comp_def, name) + end # check that a time dimension has been set if ! has_dim(obj, :time) @@ -742,18 +800,20 @@ end """ add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, - first=nothing, last=nothing, before=nothing, after=nothing) + exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -@method function add_comp!(obj::CompositeComponentDef, comp_id::ComponentId, +function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; + exports=nothing, first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") - add_comp!(obj, compdef(comp_id), comp_name, first=first, last=last, before=before, after=after) + add_comp!(obj, compdef(comp_id), comp_name, + exports=exports, first=first, last=last, before=before, after=after) end """ @@ -769,7 +829,7 @@ added with the same first and last values, unless the keywords `first` or `last` Optional boolean argument `reconnect` with default value `true` indicates whether the existing parameter connections should be maintained in the new component. """ -@method function replace_comp!(obj::CompositeComponentDef, comp_id::ComponentId, +function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing, @@ -861,11 +921,26 @@ function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) return obj end - name = path[1] + name = path.names[1] if has_comp(obj, name) - return find_comp(compdef(obj, name), path[2:end]) + return find_comp(compdef(obj, name), ComponentPath(path.names[2:end])) end return nothing end +find_comp(obj::AbstractComponentDef, name::Symbol) = find_comp(obj, ComponentPath(name)) + find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) + +function find_comp(path::ComponentPath) + names = path.names + if isempty(names) + error("Can't find component: ComponentPath is empty") + end + + if (md = find_model_def(names[1])) === nothing + error("Can't find a ModelDef named $(names[1])") + end + + find_comp(md, ComponentPath(names[2:end])) +end \ No newline at end of file diff --git a/src/core/instances.jl b/src/core/instances.jl index aae11b23d..98755905e 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -10,12 +10,12 @@ Return the `ModelDef` contained by ModelInstance `mi`. modeldef(mi::ModelInstance) = mi.md """ - @method add_comp!(obj::CompositeComponentInstance, ci::AbstractComponentInstance) + add_comp!(obj::AbstractCompositeComponentInstance, ci::AbstractComponentInstance) Add the (leaf or composite) component `ci` to a composite's list of components, and add the `first` and `last` of `mi` to the ends of the composite's `firsts` and `lasts` lists. """ -@method function add_comp!(obj::CompositeComponentInstance, ci::AbstractComponentInstance) +function add_comp!(obj::AbstractCompositeComponentInstance, ci::AbstractComponentInstance) obj.comps_dict[nameof(ci)] = ci # push!(obj.firsts, first_period(ci)) # TBD: perhaps this should be set when time is set? @@ -75,14 +75,14 @@ end end end -@method comp_paths(obj::ComponentInstanceData) = getfield(obj, :comp_paths) +comp_paths(obj::AbstractComponentInstanceData) = getfield(obj, :comp_paths) """ get_param_value(ci::ComponentInstance, name::Symbol) Return the value of parameter `name` in (leaf or composite) component `ci`. """ -@method function get_param_value(ci::ComponentInstance, name::Symbol) +function get_param_value(ci::AbstractComponentInstance, name::Symbol) try return getproperty(ci.parameters, name) catch err @@ -99,7 +99,7 @@ end Return the value of variable `name` in component `ci`. """ -@method function get_var_value(ci::ComponentInstance, name::Symbol) +function get_var_value(ci::AbstractComponentInstance, name::Symbol) try # println("Getting $name from $(ci.variables)") return getproperty(ci.variables, name) @@ -112,16 +112,16 @@ Return the value of variable `name` in component `ci`. end end -@method set_param_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) +set_param_value(ci::AbstractComponentInstance, name::Symbol, value) = setproperty!(ci.parameters, name, value) -@method set_var_value(ci::ComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) +set_var_value(ci::AbstractComponentInstance, name::Symbol, value) = setproperty!(ci.variables, name, value) """ variables(obj::AbstractCompositeComponentInstance, comp_name::Symbol) Return the `ComponentInstanceVariables` for `comp_name` in CompositeComponentInstance `obj`. """ -@method variables(obj::CompositeComponentInstance, comp_name::Symbol) = variables(compinstance(obj, comp_name)) +variables(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = variables(compinstance(obj, comp_name)) function variables(m::Model) if ! is_built(m) @@ -135,7 +135,7 @@ end Return the `ComponentInstanceParameters` for `comp_name` in CompositeComponentInstance `obj`. """ -@method parameters(obj::CompositeComponentInstance, comp_name::Symbol) = parameters(compinstance(obj, comp_name)) +parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = parameters(compinstance(obj, comp_name)) function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) if ! has_comp(mi, comp_name) @@ -166,7 +166,7 @@ Return the size of index `dim_name`` in model instance `mi`. """ @delegate dim_count(mi::ModelInstance, dim_name::Symbol) => md -@method function reset_variables(ci::ComponentInstance) +function reset_variables(ci::AbstractComponentInstance) # @info "reset_variables($(ci.comp_id))" vars = ci.variables @@ -185,14 +185,14 @@ Return the size of index `dim_name`` in model instance `mi`. end end -@method function reset_variables(obj::CompositeComponentInstance) +function reset_variables(obj::AbstractCompositeComponentInstance) for ci in components(obj) reset_variables(ci) end return nothing end -@method function init(ci::ComponentInstance, dims::DimValueDict) +function init(ci::AbstractComponentInstance, dims::DimValueDict) # @info "init($(ci.comp_id))" reset_variables(ci) @@ -202,16 +202,16 @@ end return nothing end -@method function init(obj::CompositeComponentInstance, dims::DimValueDict) +function init(obj::AbstractCompositeComponentInstance, dims::DimValueDict) for ci in components(obj) init(ci, dims) end return nothing end -@method _runnable(ci::ComponentInstance, clock::Clock) = (ci.first <= gettime(clock) <= ci.last) +_runnable(ci::AbstractComponentInstance, clock::Clock) = (ci.first <= gettime(clock) <= ci.last) -@method function run_timestep(ci::ComponentInstance, clock::Clock, dims::DimValueDict) +function run_timestep(ci::AbstractComponentInstance, clock::Clock, dims::DimValueDict) if ci.run_timestep !== nothing && _runnable(ci, clock) ci.run_timestep(ci.parameters, ci.variables, dims, clock.ts) end @@ -219,7 +219,7 @@ end return nothing end -@method function run_timestep(cci::CompositeComponentInstance, clock::Clock, dims::DimValueDict) +function run_timestep(cci::AbstractCompositeComponentInstance, clock::Clock, dims::DimValueDict) if _runnable(cci, clock) for ci in components(cci) run_timestep(ci, clock, dims) diff --git a/src/core/model.jl b/src/core/model.jl index edd4521a5..d0d970ed5 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -25,7 +25,7 @@ is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate external_param_conns(m::Model) => md @delegate external_params(m::Model) => md -@delegate external_param(m::Model, name::Symbol) => md +@delegate external_param(m::Model, name::Symbol; missing_ok=false) => md @delegate connected_params(m::Model, comp_name::Symbol) => md @delegate unconnected_params(m::Model) => md @@ -108,15 +108,15 @@ model definition. """ add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; - first=nothing, last=nothing, before=nothing, after=nothing) + exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_id` to the model indicated by `m`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the new name. """ function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - first=nothing, last=nothing, before=nothing, after=nothing) - add_comp!(m.md, comp_id, comp_name; first=first, last=last, before=before, after=after) + exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) + add_comp!(m.md, comp_id, comp_name; exports=exports, first=first, last=last, before=before, after=after) return ComponentReference(m, comp_name) end @@ -168,11 +168,11 @@ Return an iterator on the components in a model's model instance. Return a DatumDef for `item` in the given component `comp_def`. """ function datumdef(comp_def::ComponentDef, item::Symbol) - if haskey(comp_def.variables, item) - return comp_def.variables[item] + if has_variable(comp_def, item) + return variable(comp_def, item) - elseif haskey(comp_def.parameters, item) - return comp_def.parameters[item] + elseif has_parameter(comp_def, item) + return parameter(comp_def, item) else error("Cannot access data item; :$item is not a variable or a parameter in component $(comp_def.comp_id).") end diff --git a/src/core/show.jl b/src/core/show.jl index baf6b8368..0468ea319 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -71,6 +71,20 @@ function _show_field(io::IO, name::Symbol, vec::Vector{T}; show_empty=true) wher nothing end +function _show_field(io::IO, name::Symbol, vec::Vector{<: AbstractMimiType}; show_empty=true) + if !show_empty && isempty(vec) + return + end + + print(io, "\n") + print_indented(io, name, ": ") + io = indent(io) + for (i, value) in enumerate(vec) + print(io, "\n") + print_indented(io, "$i: ", value) + end +end + function _show_fields(io::IO, obj, names; show_empty=true) for name in names value = getfield(obj, name) @@ -91,10 +105,16 @@ function _show_datum_def(io::IO, obj::AbstractDatumDef) end function show(io::IO, obj::ComponentId) - print(io, "") + print(io, "ComponentId($(obj.module_name).$(obj.comp_name))") + nothing +end + +function show(io::IO, obj::ComponentPath) + print(io, "ComponentPath$(obj.names)") nothing end + function show(io::IO, obj::AbstractDimension) print(io, keys(obj)) nothing @@ -130,5 +150,8 @@ function show(io::IO, obj::ModelInstance) pos = findfirst(x -> x == :md, fields) fields = deleteat!([fields...], pos) - _show_fields(indent(io), obj, fields) + io = indent(io) + _show_fields(io, obj, fields) + print(io, "\n") + print_indented(io, "md: (not shown)") end \ No newline at end of file diff --git a/src/core/types.jl b/src/core/types.jl index 191c6cd76..d13e626e9 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -185,20 +185,27 @@ end # Identifies the path through multiple composites to a leaf component # TBD: Could be just a tuple of Symbols since they are unique at each level. -const ComponentPath = NTuple{N, Symbol} where N +struct ComponentPath <: MimiStruct + names::NTuple{N, Symbol} where N +end + +ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) + +ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) + +ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path1.names...)) + +ComponentPath(name::Symbol) = ComponentPath((name,)) -ComponentPath(names::Vector{Symbol}) = Tuple(names) +ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) + +Base.isempty(obj::ComponentPath) = isempty(obj.names) # The equivalent of ".." in the file system. -Base.parent(path::ComponentPath) = path[1:end-1] +Base.parent(path::ComponentPath) = ComponentPath(path.names[1:end-1]) ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) -# -# TBD: consider a naming protocol that adds Cls to class struct names -# so it's obvious in the code. -# - # Objects with a `name` attribute @class NamedObj <: MimiClass begin name::Symbol @@ -208,17 +215,19 @@ end nameof(obj::NamedDef) = obj.name Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, -`CompositeComponentDef`, and `DatumReference`. +`CompositeComponentDef`, and `VariableDefReference` and `ParameterDefReference`. """ -@method Base.nameof(obj::NamedObj) = obj.name +Base.nameof(obj::AbstractNamedObj) = obj.name # Stores references to the name of a component variable or parameter # and the ComponentId of the component in which it is defined @class DatumReference <: NamedObj begin - # TBD: should be a ComponentPath - comp_id::ComponentId # TBD: should this be a ComponentPath? + comp_path::ComponentPath end +@class ParameterDefReference <: DatumReference +@class VariableDefReference <: DatumReference + # Similar structure is used for variables and parameters (parameters merely adds `default`) @class mutable DatumDef <: NamedObj begin datatype::DataType @@ -273,20 +282,19 @@ end end end -@method comp_id(obj::ComponentDef) = obj.comp_id -@method dim_dict(obj::ComponentDef) = obj.dim_dict -@method first_period(obj::ComponentDef) = obj.first -@method last_period(obj::ComponentDef) = obj.last -@method isuniform(obj::ComponentDef) = obj.is_uniform +comp_id(obj::AbstractComponentDef) = obj.comp_id +dim_dict(obj::AbstractComponentDef) = obj.dim_dict +first_period(obj::AbstractComponentDef) = obj.first +last_period(obj::AbstractComponentDef) = obj.last +isuniform(obj::AbstractComponentDef) = obj.is_uniform # Define type aliases to avoid repeating these in several places -global const BindingsDef = Vector{Pair{T where T <: AbstractDatumReference, Union{Int, Float64, DatumReference}}} +global const BindingsDef = Vector{Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}}} global const ExportsDef = Dict{Symbol, AbstractDatumReference} @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::BindingsDef - exports::ExportsDef internal_param_conns::Vector{InternalParameterConnection} @@ -339,20 +347,28 @@ global const ExportsDef = Dict{Symbol, AbstractDatumReference} end # TBD: these should dynamically and recursively compute the lists -@method internal_param_conns(obj::CompositeComponentDef) = obj.internal_param_conns -@method external_param_conns(obj::CompositeComponentDef) = obj.external_param_conns +internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns +external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns + +# TBD: should only ModelDefs have external params? +external_params(obj::AbstractCompositeComponentDef) = obj.external_params -@method external_params(obj::CompositeComponentDef) = obj.external_params -@method external_param(obj::CompositeComponentDef, name::Symbol) = obj.external_params[name] +exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) +is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) -@method exported_names(obj::CompositeComponentDef) = keys(obj.exports) -@method is_exported(obj::CompositeComponentDef, name::Symbol) = haskey(obj.exports, name) +add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) -@method add_backup!(obj::CompositeComponentDef, backup) = push!(obj.backups, backup) +is_leaf(c::AbstractComponentDef) = true +is_leaf(c::AbstractCompositeComponentDef) = false +is_composite(c::AbstractComponentDef) = !is_leaf(c) -@method is_leaf(c::ComponentDef) = true -@method is_leaf(c::CompositeComponentDef) = false -@method is_composite(c::ComponentDef) = !is_leaf(c) +# Registry for ModelDef instances so we can find components by ComponentPath +_model_def_registry = WeakKeyDict() + +# Finalizer for ModelDefs +_del_model_def(md) = delete!(_model_def_registry, nameof(md)) + +find_model_def(name::Symbol) = get(_model_def_registry, name, nothing) @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType @@ -361,7 +377,13 @@ end function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - self.comp_path = (self.name,) + self.comp_path = ComponentPath(self.name) + + # Register the model and set up finalizer to delete it from + # the registry when there are no more references to it. + _model_def_registry[self.name] = self + finalizer(_del_model_def, self) + return ModelDef(self, number_type, false) end end @@ -376,10 +398,10 @@ end comp_paths::Vector{ComponentPath} # records the origin of each datum end -@method nt(obj::ComponentInstanceData) = getfield(obj, :nt) -@method types(obj::ComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters -@method Base.names(obj::ComponentInstanceData) = keys(nt(obj)) -@method Base.values(obj::ComponentInstanceData) = values(nt(obj)) +nt(obj::AbstractComponentInstanceData) = getfield(obj, :nt) +types(obj::AbstractComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters +Base.names(obj::AbstractComponentInstanceData) = keys(nt(obj)) +Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) # Centralizes the shared functionality from the two component data subtypes. function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, @@ -491,7 +513,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro end end -@method function ComponentInstance(comp_def::ComponentDef, vars::TV, pars::TP, +function ComponentInstance(comp_def::AbstractComponentDef, vars::TV, pars::TP, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} @@ -501,12 +523,11 @@ end end # These can be called on CompositeComponentInstances and ModelInstances -@method compdef(obj::ComponentInstance) = compdef(comp_id(obj)) -# @method dim_value_dict(obj::ComponentInstance) = obj.dim_value_dict -@method has_dim(obj::ComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) -@method dimension(obj::ComponentInstance, name::Symbol) = obj.dim_value_dict[name] -@method first_period(obj::ComponentInstance) = obj.first -@method last_period(obj::ComponentInstance) = obj.last +compdef(obj::AbstractComponentInstance) = compdef(comp_id(obj)) +has_dim(obj::AbstractComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) +dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[name] +first_period(obj::AbstractComponentInstance) = obj.first +last_period(obj::AbstractComponentInstance) = obj.last @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} @@ -540,13 +561,13 @@ end end # These methods can be called on ModelInstances as well -@method components(obj::CompositeComponentInstance) = values(obj.comps_dict) -@method has_comp(obj::CompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) -@method compinstance(obj::CompositeComponentInstance, name::Symbol) = obj.comps_dict[name] +components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) +has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) +compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] -@method is_leaf(ci::ComponentInstance) = true -@method is_leaf(ci::CompositeComponentInstance) = false -@method is_composite(ci::ComponentInstance) = !is_leaf(ci) +is_leaf(ci::AbstractComponentInstance) = true +is_leaf(ci::AbstractCompositeComponentInstance) = false +is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) # # TBD: Should include only exported vars and pars, right? diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index eceb21c92..89d96cc20 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -4,7 +4,8 @@ using Test using Mimi import Mimi: - compdef, compname, reset_compdefs, compmodule, first_period, last_period + compdef, compname, reset_compdefs, compmodule, first_period, last_period, + variable_names reset_compdefs() @@ -37,10 +38,17 @@ end component(ch4forcing1, ch4forcing2) # add another one with a different name end +# this returns the "registered" version defined by @defcomp +c0 = compdef(ch4forcing1) +@test compmodule(c0) == :TestMetaInfo +@test compname(c0) == :ch4forcing1 +@test nameof(c0) == :ch4forcing1 + +# These are deepcopies of c0 that are added to test_model c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) -@test c1 == compdef(:ch4forcing1) +@test variable_names(c1) == variable_names(c0) @test_throws ErrorException compdef(:missingcomp) @test compmodule(c2) == :TestMetaInfo diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index fdd4817c8..65129fbcf 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -40,9 +40,6 @@ end c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) -@test c1 == compdef(:ch4forcing1) -@test_throws ErrorException compdef(:missingcomp) - @test compmodule(c2) == :TestMetaInfo_VariableTimestep @test compname(c2) == :ch4forcing1 @test nameof(c2) == :ch4forcing2 diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index e236270f0..fe3a24045 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -49,7 +49,7 @@ m = Model() set_dimension!(m, :time, 2000:2005) add_comp!(m, X) # Original component X set_param!(m, :X, :x, zeros(6)) -replace_comp!(m, X_repl, :X) # Successfully replaced by X_repl +replace_comp!(m, X_repl, :X) # Replace X with X_repl run(m) @test length(components(m)) == 1 # Only one component exists in the model @test m[:X, :y] == 2 * ones(6) # Successfully ran the run_timestep function from X_repl diff --git a/test/test_tmp.jl b/test/test_tmp.jl new file mode 100644 index 000000000..af560381d --- /dev/null +++ b/test/test_tmp.jl @@ -0,0 +1,46 @@ +module Tmp + +using Test +using Mimi +import Mimi: + reset_compdefs, compdefs, compdef, external_param_conns + +reset_compdefs() + +@defcomp X begin + x = Parameter(index = [time]) + y = Variable(index = [time]) + function run_timestep(p, v, d, t) + v.y[t] = 1 + end +end + +@defcomp X_repl begin + x = Parameter(index = [time]) + y = Variable(index = [time]) + function run_timestep(p, v, d, t) + v.y[t] = 2 + end +end + +m = Model() +set_dimension!(m, :time, 2000:2005) +add_comp!(m, X, exports=[:x => :z]) # Original component X +add_comp!(m, X_repl) +set_param!(m, :X, :x, zeros(6)) + +if false + run(m) + @test m[:X, :y] == ones(6) + + replace_comp!(m, X_repl, :X) + run(m) + + @test length(components(m)) == 1 # Only one component exists in the model + @test m[:X, :y] == 2 * ones(6) # Successfully ran the run_timestep function from X_repl +end + +end # module + +using Mimi +m = Tmp.m From f2aa318db66f792f31529404a1c1dcda2527a137 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 13 Feb 2019 17:57:51 -0800 Subject: [PATCH 34/81] WIP commit. --- src/core/connections.jl | 12 ++-- src/core/defs.jl | 140 +++++++++++++++++++++------------------- src/core/instances.jl | 13 ++-- src/core/model.jl | 7 +- src/core/references.jl | 6 +- src/core/show.jl | 22 +++++++ src/core/types.jl | 137 +++++++++++++++++---------------------- test/test_composite.jl | 42 +++++++----- 8 files changed, 200 insertions(+), 179 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 753f059de..a3c96adca 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -87,12 +87,12 @@ end src_comp_name::Symbol, src_var_name::Symbol backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) -Bind the parameter `dst_par_name` of one component `dst_comp_name` of model `md` -to a variable `src_var_name` in another component `src_comp_name` of the same model -using `backup` to provide default values and the `ignoreunits` flag to indicate the need -to check match units between the two. The `offset` argument indicates the offset between the destination -and the source ie. the value would be `1` if the destination component parameter -should only be calculated for the second timestep and beyond. +Bind the parameter `dst_par_name` of one component `dst_comp_name` of model `md` to a +variable `src_var_name` in another component `src_comp_name` of the same model using +`backup` to provide default values and the `ignoreunits` flag to indicate the need to +check match units between the two. The `offset` argument indicates the offset between +the destination and the source ie. the value would be `1` if the destination component +parameter should only be calculated for the second timestep and beyond. """ function connect_param!(md::ModelDef, dst_comp_name::Symbol, dst_par_name::Symbol, diff --git a/src/core/defs.jl b/src/core/defs.jl index 324684818..be50b12a2 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -18,9 +18,9 @@ function compdef(comp_name::Symbol) end end -@delegate compdef(dr::AbstractDatumReference) => comp_path +compdef(dr::AbstractDatumReference) = compdef(dr.root, dr.comp_path) -compdef(path::ComponentPath) = find_comp(path) +compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] @@ -38,6 +38,10 @@ compname(obj::AbstractComponentDef) = compname(obj.comp_id) compnames() = map(compname, compdefs()) + +# Access a subcomponent as comp[:name] +Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict[name] + function reset_compdefs(reload_builtins=true) empty!(_compdefs) @@ -54,14 +58,11 @@ end dirty(md::ModelDef) = md.dirty function dirty!(obj::AbstractComponentDef) - path = obj.comp_path - if (path === nothing || isempty(path)) + root = get_root(obj) + if root === nothing return end - root = compdef(path[1]) - - # test is necessary to avoid looping when length(path) == 1 if root isa ModelDef dirty!(root) end @@ -394,7 +395,7 @@ function parameters(ccd::AbstractCompositeComponentDef) # return cached parameters, if any if length(pars) == 0 for (name, dr) in ccd.exports - cd = compdef(dr.comp_id) + cd = compdef(dr) if has_parameter(cd, nameof(dr)) pars[name] = parameter(cd, nameof(dr)) end @@ -666,24 +667,79 @@ end function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, name::Symbol) path = ComponentPath(parent.comp_path, comp_def.name) + root = get_root(parent) if has_variable(comp_def, name) - return VariableDefReference(name, path) + return VariableDefReference(name, root, path) end if has_parameter(comp_def, name) - return ParameterDefReference(name, path) + return ParameterDefReference(name, root, path) end error("$(comp_def.comp_path) does not have a data item named $name") end +# Save a back-pointer to the container object +function parent!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) + child.parent = parent + nothing +end + +# Recursively ascend the component tree structure to find the root node +get_root(node::AbstractComponentDef) = (node.parent === nothing ? node : get_root(node.parent)) + const NothingInt = Union{Nothing, Int} const NothingSymbol = Union{Nothing, Symbol} const ExportList = Vector{Union{Symbol, Pair{Symbol, Symbol}}} +function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef; + before::NothingSymbol=nothing, after::NothingSymbol=nothing) + + comp_name = nameof(comp_def) + + if before === nothing && after === nothing + _append_comp!(obj, comp_name, comp_def) # add it to the end + else + new_comps = OrderedDict{Symbol, AbstractComponentDef}() + + if before !== nothing + if ! has_comp(obj, before) + error("Component to add before ($before) does not exist") + end + + for (k, v) in obj.comps_dict + if k == before + new_comps[comp_name] = comp_def + end + new_comps[k] = v + end + + else # after !== nothing, since we've handled all other possibilities above + if ! has_comp(obj, after) + error("Component to add before ($before) does not exist") + end + + for (k, v) in obj.comps_dict + new_comps[k] = v + if k == after + new_comps[comp_name] = comp_def + end + end + end + + comps_dict!(obj, new_comps) + end + + comp_path!(obj, comp_def) + dirty!(obj) + + nothing +end + + """ - add_comp!(md::ModelDef, comp_def::ComponentDef; + add_comp!(obj::AbstractCompositeComponentDef, comp_def::ComponentDef; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component @@ -695,9 +751,9 @@ the second element allows this var/par to have a new name in the composite. A sy the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; - exports=nothing, - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) + exports=nothing, + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing) # if not specified, export all var/pars. Caller can pass empty list to export nothing. if exports === nothing @@ -744,44 +800,11 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone # Copy the original so we don't step on other uses of this comp comp_def = deepcopy(comp_def) comp_def.name = comp_name + parent!(comp_def, obj) set_run_period!(comp_def, first, last) - _add_anonymous_dims!(obj, comp_def) - - if before === nothing && after === nothing - _append_comp!(obj, comp_name, comp_def) # just add it to the end - else - new_comps = OrderedDict{Symbol, AbstractComponentDef}() - - if before !== nothing - if ! has_comp(obj, before) - error("Component to add before ($before) does not exist") - end - - for k in compkeys(obj) - if k == before - new_comps[comp_name] = comp_def - end - new_comps[k] = compdef(obj, k) - end - - else # after !== nothing, since we've handled all other possibilities above - if ! has_comp(obj, after) - error("Component to add before ($before) does not exist") - end - - for k in compkeys(obj) - new_comps[k] = compdef(obj, k) - if k == after - new_comps[comp_name] = comp_def - end - end - end - - comps_dict!(obj, new_comps) - # println("obj.comp_defs: $(comp_defs(obj))") - end + _insert_comp!(obj, comp_def, before=before, after=after) # Set parameters to any specified defaults for param in parameters(comp_def) @@ -789,11 +812,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone set_param!(obj, comp_name, nameof(param), param.default) end end - - comp_path!(obj, comp_def) - - dirty!(obj) - + # Return the comp since it's a copy of what was passed in return comp_def end @@ -931,16 +950,3 @@ end find_comp(obj::AbstractComponentDef, name::Symbol) = find_comp(obj, ComponentPath(name)) find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) - -function find_comp(path::ComponentPath) - names = path.names - if isempty(names) - error("Can't find component: ComponentPath is empty") - end - - if (md = find_model_def(names[1])) === nothing - error("Can't find a ModelDef named $(names[1])") - end - - find_comp(md, ComponentPath(names[2:end])) -end \ No newline at end of file diff --git a/src/core/instances.jl b/src/core/instances.jl index 98755905e..b07cbe744 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -137,12 +137,15 @@ Return the `ComponentInstanceParameters` for `comp_name` in CompositeComponentIn """ parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = parameters(compinstance(obj, comp_name)) -function Base.getindex(mi::ModelInstance, comp_name::Symbol, datum_name::Symbol) - if ! has_comp(mi, comp_name) - error("Component :$comp_name does not exist in current model") +function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol) + if ! has_comp(obj, comp_name) + error("Component :$comp_name does not exist in the given composite") end - - comp_inst = compinstance(mi, comp_name) + return compinstance(obj, comp_name) +end + +function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol, datum_name::Symbol) + comp_inst = obj[comp_name] vars = comp_inst.variables pars = comp_inst.parameters diff --git a/src/core/model.jl b/src/core/model.jl index d0d970ed5..c0bc887c2 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -17,7 +17,7 @@ modelinstance_def(m::Model) = modeldef(modelinstance(m)) is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate compinstance(m::Model, name::Symbol) => mi -@delegate has_comp(m::Model, name::Symbol) => mi +@delegate has_comp(m::Model, name::Symbol) => md @delegate number_type(m::Model) => md @@ -116,8 +116,8 @@ differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the """ function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) - add_comp!(m.md, comp_id, comp_name; exports=exports, first=first, last=last, before=before, after=after) - return ComponentReference(m, comp_name) + comp_def = add_comp!(m.md, comp_id, comp_name; exports=exports, first=first, last=last, before=before, after=after) + return ComponentReference(m.md, comp_def.comp_path) end """ @@ -295,4 +295,3 @@ function Base.run(m::Model; ntimesteps::Int=typemax(Int), rebuild::Bool=false, run(mi, ntimesteps, dim_keys) nothing end - \ No newline at end of file diff --git a/src/core/references.jl b/src/core/references.jl index 3d2156add..9172b22d6 100644 --- a/src/core/references.jl +++ b/src/core/references.jl @@ -41,7 +41,7 @@ end Get a variable reference as `comp_ref[var_name]`. """ function Base.getindex(comp_ref::ComponentReference, var_name::Symbol) - VariableReference(comp_ref.model, comp_ref.comp_name, var_name) + VariableReference(comp_ref, var_name) end """ @@ -50,7 +50,7 @@ end Connect two components as `comp_ref[var_name] = var_ref`. """ function Base.setindex!(comp_ref::ComponentReference, var_ref::VariableReference, var_name::Symbol) - comp_ref.model == var_ref.model || error("Can't connect variables defined in different models") + same_composite(comp_ref, var_ref)|| error("Can't connect variables defined in different composite trees") - connect_param!(comp_ref.model, comp_ref.comp_name, var_name, var_ref.comp_name, var_ref.var_name) + connect_param!(comp_ref.parent, comp_ref.comp_name, var_name, var_ref.comp_name, var_ref.var_name) end diff --git a/src/core/show.jl b/src/core/show.jl index 0468ea319..92c90e3e2 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -138,9 +138,31 @@ function show(io::IO, obj::AbstractMimiType) print(io, "($(obj.name))") fields = deleteat!([fields...], pos) end + _show_fields(indent(io), obj, fields) end +function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) + # Don't print parent or root since these create circular references + print(io, nameof(typeof(obj))) + + fields = fieldnames(typeof(obj)) + + for field in (:parent, :root) + pos = findfirst(x -> x == field, fields) + if pos !== nothing + value = getfield(obj, field) + name = (value === nothing ? "nothing" : nameof(value)) + fields = deleteat!([fields...], pos) + print(io, "\n") + print_indented(indent(io), "$field: $name") + end + end + + io = indent(io) + _show_fields(io, obj, fields) +end + function show(io::IO, obj::ModelInstance) # Don't print full type signature since it's shown in .variables and .parameters print(io, "ModelInstance") diff --git a/src/core/types.jl b/src/core/types.jl index d13e626e9..9ca828a10 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -219,14 +219,9 @@ Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, """ Base.nameof(obj::AbstractNamedObj) = obj.name -# Stores references to the name of a component variable or parameter -# and the ComponentId of the component in which it is defined -@class DatumReference <: NamedObj begin - comp_path::ComponentPath -end - -@class ParameterDefReference <: DatumReference -@class VariableDefReference <: DatumReference +# TBD: if DatumReference refers to the "registered" components, then ComponentId +# is adequate for locating it. As David suggested, having separate types for the +# registered components and the user's ModelDef structure would be clarifying. # Similar structure is used for variables and parameters (parameters merely adds `default`) @class mutable DatumDef <: NamedObj begin @@ -253,6 +248,10 @@ end last::Union{Nothing, Int} is_uniform::Bool + # Store a reference to the AbstractCompositeComponent that contains this comp def. + # That type isn't defined yet, so we declare Any here. + parent::Union{Nothing, Any} + function ComponentDef(self::ComponentDef, comp_id::Nothing) error("Leaf ComponentDef objects must have a valid ComponentId name (not nothing)") end @@ -272,6 +271,7 @@ end self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.first = self.last = nothing self.is_uniform = true + self.parent = nothing return self end @@ -288,14 +288,25 @@ first_period(obj::AbstractComponentDef) = obj.first last_period(obj::AbstractComponentDef) = obj.last isuniform(obj::AbstractComponentDef) = obj.is_uniform +# Stores references to the name of a component variable or parameter +# and the ComponentPath of the component in which it is defined +@class DatumReference <: NamedObj begin + root::AbstractComponentDef + comp_path::ComponentPath +end + +@class ParameterDefReference <: DatumReference +@class VariableDefReference <: DatumReference + + # Define type aliases to avoid repeating these in several places -global const BindingsDef = Vector{Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}}} -global const ExportsDef = Dict{Symbol, AbstractDatumReference} +global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} +global const ExportsDict = Dict{Symbol, AbstractDatumReference} @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} - bindings::BindingsDef - exports::ExportsDef + bindings::Vector{Binding} + exports::ExportsDict internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} @@ -306,45 +317,37 @@ global const ExportsDef = Dict{Symbol, AbstractDatumReference} sorted_comps::Union{Nothing, Vector{Symbol}} - function CompositeComponentDef(self::AbstractCompositeComponentDef, - comp_id::ComponentId, - comps::Vector{<: AbstractComponentDef}, - bindings::BindingsDef, - exports::ExportsDef) - - # TBD: OrderedDict{ComponentId, AbstractComponentDef} - comps_dict = OrderedDict{Symbol, AbstractComponentDef}([nameof(cd) => cd for cd in comps]) - in_conns = Vector{InternalParameterConnection}() - ex_conns = Vector{ExternalParameterConnection}() - ex_params = Dict{Symbol, ModelParameter}() - backups = Vector{Symbol}() - sorted_comps = nothing - - ComponentDef(self, comp_id) # superclass init [TBD: allow for alternate comp_name?] - CompositeComponentDef(self, comps_dict, bindings, exports, in_conns, ex_conns, - ex_params, backups, sorted_comps) + function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}=nothing) + self = new() + CompositeComponentDef(self, comp_id) return self end - function CompositeComponentDef(comp_id::ComponentId, comps::Vector{<: AbstractComponentDef}, - bindings::BindingsDef, - exports::ExportsDef) - - self = new() - return CompositeComponentDef(self, comp_id, comps, bindings, exports) + function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::Union{Nothing, ComponentId}=nothing) + ComponentDef(self, comp_id) # call superclass' initializer + + self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() + self.bindings = Vector{Binding}() + self.exports = ExportsDict() + self.internal_param_conns = Vector{InternalParameterConnection}() + self.external_param_conns = Vector{ExternalParameterConnection}() + self.external_params = Dict{Symbol, ModelParameter}() + self.backups = Vector{Symbol}() + self.sorted_comps = nothing end +end - # Creates an empty composite compdef with all containers allocated but empty - function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) - self = (self === nothing ? new() : self) +# Deprecated? +# # Create an empty composite compdef with all containers allocated but empty +# function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) +# self = (self === nothing ? new() : self) - comp_id = ComponentId(@__MODULE__, gensym(nameof(typeof(self)))) - comps = Vector{T where T <: AbstractComponentDef}() - bindings = BindingsDef() - exports = ExportsDef() - return CompositeComponentDef(self, comp_id, comps, bindings, exports) - end -end +# comp_id = ComponentId(@__MODULE__, gensym(nameof(typeof(self)))) +# comps = Vector{T where T <: AbstractComponentDef}() +# bindings = Vector{Binding}() +# exports = ExportsDict() +# return CompositeComponentDef(self, comp_id, comps, bindings, exports) +# end # TBD: these should dynamically and recursively compute the lists internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns @@ -362,14 +365,6 @@ is_leaf(c::AbstractComponentDef) = true is_leaf(c::AbstractCompositeComponentDef) = false is_composite(c::AbstractComponentDef) = !is_leaf(c) -# Registry for ModelDef instances so we can find components by ComponentPath -_model_def_registry = WeakKeyDict() - -# Finalizer for ModelDefs -_del_model_def(md) = delete!(_model_def_registry, nameof(md)) - -find_model_def(name::Symbol) = get(_model_def_registry, name, nothing) - @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType dirty::Bool @@ -377,13 +372,7 @@ find_model_def(name::Symbol) = get(_model_def_registry, name, nothing) function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - self.comp_path = ComponentPath(self.name) - - # Register the model and set up finalizer to delete it from - # the registry when there are no more references to it. - _model_def_registry[self.name] = self - finalizer(_del_model_def, self) - + self.comp_path = ComponentPath(self.name) return ModelDef(self, number_type, false) end end @@ -671,24 +660,18 @@ end # 7. Reference types provide more convenient syntax for interrogating Components # -""" - ComponentReference - -A container for a component, for interacting with it within a model. -""" -struct ComponentReference <: MimiStruct - model::Model - comp_name::Symbol +# A container for a component, for interacting with it within a model. +@class ComponentReference <: MimiClass begin + parent::AbstractComponentDef + comp_path::ComponentPath end -""" - VariableReference - -A container for a variable within a component, to improve connect_param! aesthetics, -by supporting subscripting notation via getindex & setindex . -""" -struct VariableReference <: MimiStruct - model::Model - comp_name::Symbol +# A container for a variable within a component, to improve connect_param! aesthetics, +# by supporting subscripting notation via getindex & setindex . +@class VariableReference <: ComponentReference begin var_name::Symbol end + +function same_composite(ref1::AbstractComponentReference, ref2::AbstractComponentReference) + return ref1.comp_path[1] == ref2.comp_path[1] +end diff --git a/test/test_composite.jl b/test/test_composite.jl index e95543814..a4726af9c 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -5,7 +5,7 @@ using Mimi import Mimi: ComponentId, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, - BindingTypes, ModelDef, build, time_labels, reset_compdefs, compdef + Binding, ExportsDict, ModelDef, build, time_labels, reset_compdefs, compdef reset_compdefs() @@ -48,25 +48,33 @@ let calling_module = @__MODULE__ ccname = :testcomp ccid = ComponentId(calling_module, ccname) - comps = AbstractComponentDef[compdef(Comp1), compdef(Comp2), compdef(Comp3)] + comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] # TBD: need to implement this to create connections and default value - bindings::Vector{Pair{DatumReference, BindingTypes}} = [ - DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 - DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) - ] - - exports = [ - DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - DatumReference(:par_2_2, Comp2) => :c2p2, - DatumReference(:var_3_1, Comp3) => :c3v1 - ] - - m.md = md = ModelDef() - CompositeComponentDef(md, ccid, comps, bindings, exports) - + bindings = Binding[] + # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 + # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) + # ] + + exports = [] + # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + # DatumReference(:par_2_2, Comp2) => :c2p2, + # DatumReference(:var_3_1, Comp3) => :c3v1 + # ] + + CompositeComponentDef(m.md, ccid) # , comps, bindings, exports) + set_dimension!(m, :time, 2005:2020) + + md = m.md + for c in comps + add_comp!(md, c, nameof(c)) # later allow pair for renaming + end + + merge!(md.exports, ExportsDict(exports)) + append!(md.bindings, bindings) + nothing end From d7deb333633c7f149420171546fc9bdd6e6fd136 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 15 Feb 2019 09:37:35 -0800 Subject: [PATCH 35/81] WIP - Use comp paths rather than names to identify vars and params in components - Make several methods operate on composites rather than ModelDefs only - Move @defmodel into its own file --- .../02-two-region-model/two-region-model.jl | 7 +- src/Mimi.jl | 1 + src/core/build.jl | 83 ++--- src/core/connections.jl | 349 +++++++++++------- src/core/defcomp.jl | 73 ---- src/core/defcomposite.jl | 2 +- src/core/defmodel.jl | 107 ++++++ src/core/defs.jl | 117 ++++-- src/core/model.jl | 23 +- src/core/show.jl | 2 +- src/core/types.jl | 89 ++--- src/utils/graph.jl | 24 +- test/test_composite.jl | 9 +- 13 files changed, 510 insertions(+), 376 deletions(-) create mode 100644 src/core/defmodel.jl diff --git a/examples/tutorial/02-two-region-model/two-region-model.jl b/examples/tutorial/02-two-region-model/two-region-model.jl index 2864678c4..addc268f7 100644 --- a/examples/tutorial/02-two-region-model/two-region-model.jl +++ b/examples/tutorial/02-two-region-model/two-region-model.jl @@ -12,8 +12,9 @@ function construct_MyModel() m = Model() - set_dimension!(m, :time, collect(2015:5:2110)) - set_dimension!(m, :regions, [:Region1, :Region2, :Region3]) # Note that the regions of your model must be specified here + set_dimension!(m, :time, 2015:5:2110) + # Note that the regions of your model must be specified here + set_dimension!(m, :regions, [:Region1, :Region2, :Region3]) add_comp!(m, grosseconomy) add_comp!(m, emissions) @@ -21,7 +22,7 @@ function construct_MyModel() set_param!(m, :grosseconomy, :l, l) set_param!(m, :grosseconomy, :tfp, tfp) set_param!(m, :grosseconomy, :s, s) - set_param!(m, :grosseconomy, :depk,depk) + set_param!(m, :grosseconomy, :depk, depk) set_param!(m, :grosseconomy, :k0, k0) set_param!(m, :grosseconomy, :share, 0.3) diff --git a/src/Mimi.jl b/src/Mimi.jl index 05427dc16..7479a9fd4 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -53,6 +53,7 @@ include("core/build.jl") include("core/connections.jl") include("core/defs.jl") include("core/defcomp.jl") +include("core/defmodel.jl") include("core/defcomposite.jl") include("core/dimensions.jl") include("core/instances.jl") diff --git a/src/core/build.jl b/src/core/build.jl index 20b9bfbcc..5bb978a29 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -74,13 +74,13 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) end # Create ComponentInstanceVariables for a composite component from the list of exported vars -function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dict::Dict{Symbol, Any}) +function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dict::Dict{ComponentPath, Any}) names = Symbol[] values = Any[] for (name, dr) in comp_def.exports if is_variable(dr) - obj = var_dict[compname(dr)] + obj = var_dict[dr.comp_path] value = getproperty(obj, nameof(dr)) push!(names, name) push!(values, value) @@ -92,14 +92,13 @@ function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dic return ComponentInstanceVariables(names, types, values, paths) end -function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) names = Symbol[] values = Any[] for (name, dr) in comp_def.exports if is_parameter(dr) - d = par_dict[compname(dr)] - value = d[nameof(dr)] + value = par_dict[(dr.comp_path, dr.name)] push!(names, name) push!(values, value) end @@ -110,38 +109,36 @@ function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dic return ComponentInstanceParameters(names, types, values, paths) end -function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - comp_name = nameof(comp_def) - par_dict[comp_name] = Dict() - - var_dict[comp_name] = v = _instantiate_component_vars(md, comp_def) +function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{ComponentPath, Any}) + var_dict[comp_def.comp_path] = v = _instantiate_component_vars(md, comp_def) # @info "_instantiate_vars leaf $comp_name: $v" end # Creates the top-level vars for the model -function _instantiate_vars(md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - _instantiate_vars(md, md, var_dict, par_dict) +function _instantiate_vars(md::ModelDef, var_dict::Dict{ComponentPath, Any}) + _instantiate_vars(md, md, var_dict) end # Recursively instantiate all variables and store refs in the given dict. -function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - comp_name = nameof(comp_def) - par_dict[comp_name] = Dict() +function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef, var_dict::Dict{ComponentPath, Any}) + comp_path = comp_def.comp_path - # @info "_instantiate_vars composite $comp_name" + # @info "_instantiate_vars composite $comp_path" for cd in compdefs(comp_def) - _instantiate_vars(cd, md, var_dict, par_dict) + _instantiate_vars(cd, md, var_dict) end - var_dict[comp_name] = v = _combine_exported_vars(comp_def, var_dict) - # @info "composite vars for $comp_name: $v " + var_dict[comp_path] = v = _combine_exported_vars(comp_def, var_dict) + # @info "composite vars for $comp_path: $v " end # Do nothing if called on a leaf component _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing # Recursively collect all parameters with connections to allocated storage for variables -function _collect_params(comp_def::AbstractCompositeComponentDef, var_dict::Dict{Symbol, Any}, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _collect_params(comp_def::AbstractCompositeComponentDef, + var_dict::Dict{ComponentPath, Any}, + par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) # depth-first search of composites for cd in compdefs(comp_def) _collect_params(cd, var_dict, par_dict) @@ -151,17 +148,15 @@ function _collect_params(comp_def::AbstractCompositeComponentDef, var_dict::Dict # Iterate over connections to create parameters, referencing storage in vars for ipc in internal_param_conns(comp_def) - src_vars = var_dict[ipc.src_comp_name] + src_vars = var_dict[ipc.src_comp_path] var_value_obj = get_property_obj(src_vars, ipc.src_var_name) - comp_pars = par_dict[ipc.dst_comp_name] - comp_pars[ipc.dst_par_name] = var_value_obj - # @info "internal conn: $(ipc.src_comp_name).$(ipc.src_var_name) => $(ipc.dst_comp_name).$(ipc.dst_par_name)" + par_dict[(ipc.dst_comp_path, ipc.dst_par_name)] = var_value_obj + # @info "internal conn: $(ipc.src_comp_path):$(ipc.src_var_name) => $(ipc.dst_comp_path):$(ipc.dst_par_name)" end for ext in external_param_conns(comp_def) param = external_param(comp_def, ext.external_param) - comp_pars = par_dict[ext.comp_name] - comp_pars[ext.param_name] = param isa ScalarModelParameter ? param : value(param) + par_dict[(ext.comp_path, ext.param_name)] = (param isa ScalarModelParameter ? param : value(param)) # @info "external conn: $(ext.comp_name).$(ext.param_name) => $(param)" end @@ -170,48 +165,44 @@ function _collect_params(comp_def::AbstractCompositeComponentDef, var_dict::Dict for (i, backup) in enumerate(comp_def.backups) conn_comp_name = connector_comp_name(i) param = external_param(comp_def, backup) - comp_pars = par_dict[conn_comp_name] - comp_pars[:input2] = param isa ScalarModelParameter ? param : value(param) + par_dict[(conn_comp_name, :input2)] = (param isa ScalarModelParameter ? param : value(param)) # @info "backup: $conn_comp_name $param" end end -function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) - # @info "Instantiating params for $(comp_def.comp_id)" - comp_name = nameof(comp_def) - d = par_dict[comp_name] - +function _instantiate_params(comp_def::ComponentDef, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) + # @info "Instantiating params for $(comp_def.comp_path)" + comp_path = comp_def.comp_path names = parameter_names(comp_def) - vals = Any[d[name] for name in names] + vals = Any[par_dict[(comp_path, name)] for name in names] types = DataType[typeof(val) for val in vals] paths = repeat([comp_def.comp_path], length(names)) return ComponentInstanceParameters(names, types, vals, paths) end -function _instantiate_params(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Symbol, Dict{Symbol, Any}}) +function _instantiate_params(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) _combine_exported_pars(comp_def, par_dict) end # Return a built leaf or composite ComponentInstance function _build(comp_def::ComponentDef, - var_dict::Dict{Symbol, Any}, - par_dict::Dict{Symbol, Dict{Symbol, Any}}, + var_dict::Dict{ComponentPath, Any}, + par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, time_bounds::Tuple{Int, Int}) # @info "_build leaf $(comp_def.comp_id)" # @info " var_dict $(var_dict)" # @info " par_dict $(par_dict)" - comp_name = nameof(comp_def) pars = _instantiate_params(comp_def, par_dict) - vars = var_dict[comp_name] + vars = var_dict[comp_def.comp_path] return ComponentInstance(comp_def, vars, pars, time_bounds) end function _build(comp_def::AbstractCompositeComponentDef, - var_dict::Dict{Symbol, Any}, - par_dict::Dict{Symbol, Dict{Symbol, Any}}, + var_dict::Dict{ComponentPath, Any}, + par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, time_bounds::Tuple{Int, Int}) # @info "_build composite $(comp_def.comp_id)" # @info " var_dict $(var_dict)" @@ -228,15 +219,13 @@ function _build(md::ModelDef) not_set = unconnected_params(md) if ! isempty(not_set) params = join(not_set, " ") - msg = "Cannot build model; the following parameters are not set: $params" - error(msg) + error("Cannot build model; the following parameters are not set: $params") end - # TBD: key by ComponentPath since these span levels - var_dict = Dict{Symbol, Any}() # collect all var defs and - par_dict = Dict{Symbol, Dict{Symbol, Any}}() # store par values as we go + var_dict = Dict{ComponentPath, Any}() # collect all var defs and + par_dict = Dict{Tuple{ComponentPath, Symbol}, Any}() # store par values as we go - _instantiate_vars(md, var_dict, par_dict) + _instantiate_vars(md, var_dict) _collect_params(md, var_dict, par_dict) # @info "var_dict: $var_dict" diff --git a/src/core/connections.jl b/src/core/connections.jl index a3c96adca..4418c2a17 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -2,22 +2,50 @@ using LightGraphs using MetaGraphs """ - disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol) + disconnect_param!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, param_name::Symbol) Remove any parameter connections for a given parameter `param_name` in a given component -`comp_name` of model `md`. +`comp_def` which must be a direct subcomponent of composite `obj`. """ -function disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol) - # println("disconnect_param!($comp_name, $param_name)") - filter!(x -> !(x.dst_comp_name == comp_name && x.dst_par_name == param_name), internal_param_conns(md)) - filter!(x -> !(x.comp_name == comp_name && x.param_name == param_name), external_param_conns(md)) - dirty!(md) +function disconnect_param!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, param_name::Symbol) + if is_descendant(obj, comp_def) === nothing + error("Cannot disconnect a component ($comp_def.comp_path) that is not within the given composite ($(obj.comp_path))") + end + + path = comp_def.comp_path + filter!(x -> !(x.dst_comp_path == path && x.dst_par_name == param_name), internal_param_conns(obj)) + filter!(x -> !(x.comp_path == path && x.param_name == param_name), external_param_conns(obj)) + dirty!(obj) +end + +""" + disconnect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) + +Remove any parameter connections for a given parameter `param_name` in a given component +`comp_def` which must be a direct subcomponent of composite `obj`. +""" +function disconnect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) + disconnect_param!(obj, compdef(obj, comp_name), param_name) +end + +""" + disconnect_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol) + +Remove any parameter connections for a given parameter `param_name` in the component identified by +`comp_path` which must be under the composite `obj`. +""" +function disconnect_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol) + if (comp_def = find_comp(obj, comp_path, relative=false)) === nothing + return + end + disconnect_param!(obj, comp_def, param_name) end # Default string, string unit check function verify_units(unit1::AbstractString, unit2::AbstractString) = (unit1 == unit2) -function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) +function _check_labels(obj::AbstractCompositeComponentDef, + comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) param_def = parameter(comp_def, param_name) t1 = eltype(ext_param.values) @@ -40,18 +68,18 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, return nothing end - # index_values = indexvalues(md) + # index_values = indexvalues(obj) for (i, dim) in enumerate(comp_dims) if isa(dim, Symbol) param_length = size(ext_param.values)[i] if dim == :time - t = dimension(md, :time) - first = first_period(md, comp_def) - last = last_period(md, comp_def) + t = dimension(obj, :time) + first = first_period(obj, comp_def) + last = last_period(obj, comp_def) comp_length = t[last] - t[first] + 1 else - comp_length = dim_count(md, dim) + comp_length = dim_count(obj, dim) end if param_length != comp_length error("Mismatched data size for a parameter connection: dimension :$dim in $(comp_def.comp_id) has $comp_length elements; external parameter :$param_name has $param_length elements.") @@ -61,70 +89,73 @@ function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, end """ - connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) + connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) -Connect a parameter `param_name` in the component `comp_name` of model `md` to +Connect a parameter `param_name` in the component `comp_name` of composite `obj` to the external parameter `ext_param_name`. """ -function connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) - comp_def = compdef(md, comp_name) - ext_param = external_param(md, ext_param_name) +function connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) + comp_def = compdef(obj, comp_name) + comp_path = comp_def.comp_path + ext_param = external_param(obj, ext_param_name) if ext_param isa ArrayModelParameter - _check_labels(md, comp_def, param_name, ext_param) + _check_labels(obj, comp_def, param_name, ext_param) end - disconnect_param!(md, comp_name, param_name) # calls dirty!() + disconnect_param!(obj, comp_name, param_name) # calls dirty!() - conn = ExternalParameterConnection(comp_name, param_name, ext_param_name) - add_external_param_conn!(md, conn) + conn = ExternalParameterConnection(comp_path, param_name, ext_param_name) + add_external_param_conn!(obj, conn) return nothing end """ - connect_param!(md::ModelDef, dst_comp_name::Symbol, dst_par_name::Symbol, - src_comp_name::Symbol, src_var_name::Symbol backup::Union{Nothing, Array}=nothing; + connect_param!(obj::AbstractCompositeComponentDef, + dst_comp_path::ComponentPath, dst_par_name::Symbol, + src_comp_path::ComponentPath, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) -Bind the parameter `dst_par_name` of one component `dst_comp_name` of model `md` to a -variable `src_var_name` in another component `src_comp_name` of the same model using +Bind the parameter `dst_par_name` of one component `dst_comp_path` of composite `obj` to a +variable `src_var_name` in another component `src_comp_path` of the same model using `backup` to provide default values and the `ignoreunits` flag to indicate the need to check match units between the two. The `offset` argument indicates the offset between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -function connect_param!(md::ModelDef, - dst_comp_name::Symbol, dst_par_name::Symbol, - src_comp_name::Symbol, src_var_name::Symbol, +function connect_param!(obj::AbstractCompositeComponentDef, + dst_comp_path::ComponentPath, dst_par_name::Symbol, + src_comp_path::ComponentPath, src_var_name::Symbol, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) # remove any existing connections for this dst parameter - disconnect_param!(md, dst_comp_name, dst_par_name) # calls dirty!() + disconnect_param!(obj, dst_comp_path, dst_par_name) # calls dirty!() - dst_comp_def = compdef(md, dst_comp_name) - src_comp_def = compdef(md, src_comp_name) + dst_comp_def = compdef(obj, dst_comp_path) + src_comp_def = compdef(obj, src_comp_path) if backup !== nothing # If value is a NamedArray, we can check if the labels match if isa(backup, NamedArray) dims = dimnames(backup) - check_parameter_dimensions(md, backup, dims, dst_par_name) + check_parameter_dimensions(obj, backup, dims, dst_par_name) else dims = nothing end # Check that the backup data is the right size - if size(backup) != datum_size(md, dst_comp_def, dst_par_name) - error("Cannot connect parameter; the provided backup data is the wrong size. Expected size $(datum_size(md, dst_comp_def, dst_par_name)) but got $(size(backup)).") + if size(backup) != datum_size(obj, dst_comp_def, dst_par_name) + error("Cannot connect parameter; the provided backup data is the wrong size. Expected size $(datum_size(obj, dst_comp_def, dst_par_name)) but got $(size(backup)).") end # some other check for second dimension?? dst_param = parameter(dst_comp_def, dst_par_name) dst_dims = dim_names(dst_param) - backup = convert(Array{Union{Missing, number_type(md)}}, backup) # converts number type and, if it's a NamedArray, it's converted to Array - first = first_period(md, dst_comp_def) + backup = convert(Array{Union{Missing, number_type(obj)}}, backup) # converts number type and, if it's a NamedArray, it's converted to Array + first = first_period(obj, dst_comp_def) T = eltype(backup) dim_count = length(dst_dims) @@ -133,12 +164,12 @@ function connect_param!(md::ModelDef, values = backup else - if isuniform(md) + if isuniform(obj) # use the first from the comp_def not the ModelDef - stepsize = step_size(md) + stepsize = step_size(obj) values = TimestepArray{FixedTimestep{first, stepsize}, T, dim_count}(backup) else - times = time_labels(md) + times = time_labels(obj) # use the first from the comp_def first_index = findfirst(isequal(first), times) values = TimestepArray{VariableTimestep{(times[first_index:end]...,)}, T, dim_count}(backup) @@ -146,7 +177,7 @@ function connect_param!(md::ModelDef, end - set_external_array_param!(md, dst_par_name, values, dst_dims) + set_external_array_param!(obj, dst_par_name, values, dst_dims) backup_param_name = dst_par_name else @@ -155,8 +186,8 @@ function connect_param!(md::ModelDef, dst_first, dst_last = first_and_last(dst_comp_def) if (dst_first !== nothing && src_first !== nothing && dst_first < src_first) || (dst_last !== nothing && src_last !== nothing && dst_last > src_last) - error("""Cannot connect parameter: $src_comp_name runs only from $src_first to $src_last, -whereas $dst_comp_name runs from $dst_first to $dst_last. Backup data must be provided for missing years. + error("""Cannot connect parameter: $src_comp_path runs only from $src_first to $src_last, +whereas $dst_comp_path runs from $dst_first to $dst_last. Backup data must be provided for missing years. Try calling: `connect_param!(m, comp_name, par_name, comp_name, var_name, backup_data)`""") end @@ -165,60 +196,72 @@ Try calling: end # Check the units, if provided - if ! ignoreunits && ! verify_units(variable_unit(md, src_comp_name, src_var_name), - parameter_unit(md, dst_comp_name, dst_par_name)) - error("Units of $src_comp_name.$src_var_name do not match $dst_comp_name.$dst_par_name.") + if ! ignoreunits && ! verify_units(variable_unit(src_comp_def, src_var_name), + parameter_unit(dst_comp_def, dst_par_name)) + error("Units of $src_comp_path:$src_var_name do not match $dst_comp_path:$dst_par_name.") end - # println("connect($src_comp_name.$src_var_name => $dst_comp_name.$dst_par_name)") - conn = InternalParameterConnection(src_comp_name, src_var_name, dst_comp_name, dst_par_name, ignoreunits, backup_param_name, offset=offset) - add_internal_param_conn!(md, conn) + # @info "connect($src_comp_path:$src_var_name => $dst_comp_path:$dst_par_name)" + conn = InternalParameterConnection(src_comp_path, src_var_name, dst_comp_path, dst_par_name, ignoreunits, backup_param_name, offset=offset) + add_internal_param_conn!(obj, conn) return nothing end +function connect_param!(obj::AbstractCompositeComponentDef, + dst_comp_name::Symbol, dst_par_name::Symbol, + src_comp_name::Symbol, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + connect_param!(obj, ComponentPath(obj, dst_comp_name), dst_par_name, + ComponentPath(obj, src_comp_name), src_var_name, + backup; ignoreunits=ignoreunits, offset=offset) +end + """ - connect_param!(md::ModelDef, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, - backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + connect_param!(obj::AbstractCompositeComponentDef, + dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, + backup::Union{Nothing, Array}=nothing; + ignoreunits::Bool=false, offset::Int=0) -Bind the parameter `dst[2]` of one component `dst[1]` of model `md` -to a variable `src[2]` in another component `src[1]` of the same model +Bind the parameter `dst[2]` of one component `dst[1]` of composite `obj` +to a variable `src[2]` in another component `src[1]` of the same composite using `backup` to provide default values and the `ignoreunits` flag to indicate the need to check match units between the two. The `offset` argument indicates the offset between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -function connect_param!(md::ModelDef, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, - backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) - connect_param!(md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) +function connect_param!(obj::AbstractCompositeComponentDef, + dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + connect_param!(obj, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end """ - connected_params(md::ModelDef, comp_name::Symbol) + connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) -Return list of parameters that have been set for component `comp_name` in model `md`. +Return list of parameters that have been set for component `comp_name` in composite `obj`. """ -function connected_params(md::ModelDef, comp_name::Symbol) - ext_set_params = map(x->x.param_name, external_param_conns(md, comp_name)) - int_set_params = map(x->x.dst_par_name, internal_param_conns(md, comp_name)) +function connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) + ext_set_params = map(x->x.param_name, external_param_conns(obj, comp_name)) + int_set_params = map(x->x.dst_par_name, internal_param_conns(obj, comp_name)) return union(ext_set_params, int_set_params) end """ - unconnected_params(md::ModelDef) + unconnected_params(obj::AbstractCompositeComponentDef) -Return a list of tuples (componentname, parametername) of parameters -that have not been connected to a value in the model `md`. +Return a list of tuples (comp_path, parame_name) of parameters +that have not been connected to a value in the composite `obj`. """ -function unconnected_params(md::ModelDef) - unconnected = Vector{Tuple{Symbol,Symbol}}() +function unconnected_params(obj::AbstractCompositeComponentDef) + unconnected = Vector{Tuple{ComponentPath,Symbol}}() - for comp_def in compdefs(md) - comp_name = nameof(comp_def) + for comp_def in compdefs(obj) + comp_path = comp_def.comp_path params = parameter_names(comp_def) - connected = connected_params(md, comp_name) - append!(unconnected, map(x->(comp_name, x), setdiff(params, connected))) + connected = connected_params(obj, nameof(comp_def)) + append!(unconnected, map(x->(comp_path, x), setdiff(params, connected))) end return unconnected @@ -234,7 +277,10 @@ the dictionary keys are strings that match the names of unset parameters in the function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T parameters = Dict(k => v for (k, v) in parameters) - for (comp_name, param_name) in unconnected_params(md) + for (comp_path, param_name) in unconnected_params(md) + comp_def = compdef(md, comp_path) + comp_name = nameof(comp_def) + # check whether we need to set the external parameter if external_param(md, param_name, missing_ok=true) !== nothing value = parameters[string(param_name)] @@ -249,13 +295,21 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T end # Find internal param conns to a given destination component -function internal_param_conns(obj::AbstractCompositeComponentDef, dst_comp_name::Symbol) - return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(obj)) +function internal_param_conns(obj::AbstractCompositeComponentDef, dst_comp_path::ComponentPath) + return filter(x->x.dst_comp_path == dst_comp_path, internal_param_conns(obj)) +end + +function internal_param_conns(obj::AbstractCompositeComponentDef, comp_name::Symbol) + return internal_param_conns(obj, ComponentPath(obj.comp_path, comp_name)) end # Find external param conns for a given comp +function external_param_conns(obj::AbstractCompositeComponentDef, comp_path::ComponentPath) + return filter(x -> x.comp_path == comp_path, external_param_conns(obj)) +end + function external_param_conns(obj::AbstractCompositeComponentDef, comp_name::Symbol) - return filter(x -> x.comp_name == comp_name, external_param_conns(obj)) + return external_param_conns(obj, ComponentPath(obj.comp_path, comp_name)) end function external_param(obj::AbstractCompositeComponentDef, name::Symbol; missing_ok=false) @@ -287,87 +341,96 @@ function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, v dirty!(obj) end -function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) - set_external_scalar_param!(md, name, value) +function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::Number; + param_dims::Union{Nothing,Array{Symbol}} = nothing) + set_external_scalar_param!(obj, name, value) end -function set_external_param!(md::ModelDef, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; +function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, + value::Union{AbstractArray, AbstractRange, Tuple}; param_dims::Union{Nothing,Array{Symbol}} = nothing) if param_dims[1] == :time - value = convert(Array{number_type(md)}, value) + value = convert(Array{number_type(obj)}, value) num_dims = length(param_dims) - values = get_timestep_array(md, eltype(value), num_dims, value) + values = get_timestep_array(obj, eltype(value), num_dims, value) else values = value end - set_external_array_param!(md, name, values, param_dims) + set_external_array_param!(obj, name, values, param_dims) end """ - set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepVector, dims) + set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::TimestepVector, dims) Add a one dimensional time-indexed array parameter indicated by `name` and -`value` to the model `md`. In this case `dims` must be `[:time]`. +`value` to the composite `obj`. In this case `dims` must be `[:time]`. """ -function set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepVector, dims) +function set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::TimestepVector, dims) # println("set_external_array_param!: dims=$dims, setting dims to [:time]") param = ArrayModelParameter(value, [:time]) # must be :time - set_external_param!(md, name, param) + set_external_param!(obj, name, param) end """ - set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepMatrix, dims) + set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::TimestepMatrix, dims) Add a multi-dimensional time-indexed array parameter `name` with value -`value` to the model `md`. In this case `dims` must be `[:time]`. +`value` to the composite `obj`. In this case `dims` must be `[:time]`. """ -function set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepArray, dims) +function set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::TimestepArray, dims) param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims) - set_external_param!(md, name, param) + set_external_param!(obj, name, param) end """ - set_external_array_param!(m::Model, name::Symbol, value::AbstractArray, dims) + set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::AbstractArray, dims) -Add an array type parameter `name` with value `value` and `dims` dimensions to the model 'm'. +Add an array type parameter `name` with value `value` and `dims` dimensions to the composite `obj`. """ -function set_external_array_param!(md::ModelDef, name::Symbol, value::AbstractArray, dims) - numtype = number_type(md) +function set_external_array_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value::AbstractArray, dims) + numtype = number_type(obj) if !(typeof(value) <: Array{numtype}) # Need to force a conversion (simple convert may alias in v0.6) value = Array{numtype}(undef, value) end param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims) - set_external_param!(md, name, param) + set_external_param!(obj, name, param) end """ - set_external_scalar_param!(md::ModelDef, name::Symbol, value::Any) + set_external_scalar_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::Any) -Add a scalar type parameter `name` with the value `value` to the model `md`. +Add a scalar type parameter `name` with the value `value` to the composite `obj`. """ -function set_external_scalar_param!(md::ModelDef, name::Symbol, value::Any) +function set_external_scalar_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::Any) p = ScalarModelParameter(value) - set_external_param!(md, name, p) + set_external_param!(obj, name, p) end """ - update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false) + update_param!(obj::AbstractCompositeComponentDef, name::Symbol, value; update_timesteps = false) -Update the `value` of an external model parameter in ModelDef `md`, referenced +Update the `value` of an external model parameter in composite `obj`, referenced by `name`. Optional boolean argument `update_timesteps` with default value `false` indicates whether to update the time keys associated with the parameter values to match the model's time index. """ -function update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false) - _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true) +function update_param!(obj::AbstractCompositeComponentDef, name::Symbol, value; update_timesteps = false) + _update_param!(obj::AbstractCompositeComponentDef, name, value, update_timesteps; raise_error = true) end -function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true) +function _update_param!(obj::AbstractCompositeComponentDef, + name::Symbol, value, update_timesteps; raise_error = true) param = external_param(ext_params, name, missing_ok=true) if param === nothing - error("Cannot update parameter; $name not found in model's external parameters.") + error("Cannot update parameter; $name not found in composite's external parameters.") end if param isa ScalarModelParameter @@ -376,7 +439,7 @@ function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; rai end _update_scalar_param!(param, name, value) else - _update_array_param!(md, name, value, update_timesteps, raise_error) + _update_array_param!(obj, name, value, update_timesteps, raise_error) end dirty!(md) @@ -394,9 +457,9 @@ function _update_scalar_param!(param::ScalarModelParameter, name, value) nothing end -function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise_error) +function _update_array_param!(obj::AbstractCompositeComponentDef, name, value, update_timesteps, raise_error) # Get original parameter - param = external_param(md, name) + param = external_param(obj, name) # Check type of provided parameter if !(typeof(value) <: AbstractArray) @@ -412,7 +475,7 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise # Check size of provided parameter if update_timesteps && param.values isa TimestepArray - expected_size = ([length(dim_keys(md, d)) for d in dim_names(param)]...,) + expected_size = ([length(dim_keys(obj, d)) for d in dim_names(param)]...,) else expected_size = size(param.values) end @@ -424,8 +487,8 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise if param.values isa TimestepArray T = eltype(value) N = length(size(value)) - new_timestep_array = get_timestep_array(md, T, N, value) - set_external_param!(md, name, ArrayModelParameter(new_timestep_array, dim_names(param))) + new_timestep_array = get_timestep_array(obj, T, N, value) + set_external_param!(obj, name, ArrayModelParameter(new_timestep_array, dim_names(param))) elseif raise_error error("Cannot update timesteps; parameter $name is not a TimestepArray.") @@ -439,43 +502,45 @@ function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise param.values = value end end - dirty!(md) + dirty!(obj) nothing end """ - update_params!(md::ModelDef, parameters::Dict{T, Any}; update_timesteps = false) where T + update_params!(obj::AbstractCompositeComponentDef, parameters::Dict{T, Any}; + update_timesteps = false) where T For each (k, v) in the provided `parameters` dictionary, `update_param!` is called to update the external parameter by name k to value v, with optional Boolean argument update_timesteps. Each key k must be a symbol or convert to a symbol matching the name of an external parameter that already exists in the -model definition. +component definition. """ -function update_params!(md::ModelDef, parameters::Dict; update_timesteps = false) +function update_params!(obj::AbstractCompositeComponentDef, parameters::Dict; update_timesteps = false) parameters = Dict(Symbol(k) => v for (k, v) in parameters) for (param_name, value) in parameters - _update_param!(md, param_name, value, update_timesteps; raise_error = false) + _update_param!(obj, param_name, value, update_timesteps; raise_error = false) end nothing end -function add_connector_comps(md::ModelDef) - conns = internal_param_conns(md) +function add_connector_comps(obj::AbstractCompositeComponentDef) + conns = internal_param_conns(obj) - for comp_def in compdefs(md) + for comp_def in compdefs(obj) comp_name = nameof(comp_def) + comp_path = comp_def.comp_path # first need to see if we need to add any connector components for this component - internal_conns = filter(x -> x.dst_comp_name == comp_name, conns) + internal_conns = filter(x -> x.dst_comp_path == comp_path, conns) need_conn_comps = filter(x -> x.backup !== nothing, internal_conns) # println("Need connectors comps: $need_conn_comps") for (i, conn) in enumerate(need_conn_comps) - add_backup!(md, conn.backup) + add_backup!(obj, conn.backup) - num_dims = length(size(external_param(md, conn.backup).values)) + num_dims = length(size(external_param(obj, conn.backup).values)) if ! (num_dims in (1, 2)) error("Connector components for parameters with > 2 dimensions are not implemented.") @@ -487,23 +552,23 @@ function add_connector_comps(md::ModelDef) conn_comp_name = connector_comp_name(i) # generate a new name # Add the connector component before the user-defined component that required it - # println("add_connector_comps: add_comp!(md, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)") - add_comp!(md, conn_comp_def, conn_comp_name, before=comp_name) + # @info "add_connector_comps: add_comp!(obj, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)" + add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) # add a connection between src_component and the ConnectorComp - add_internal_param_conn!(md, InternalParameterConnection(conn.src_comp_name, conn.src_var_name, - conn_comp_name, :input1, - conn.ignoreunits)) + add_internal_param_conn!(obj, InternalParameterConnection(conn.src_comp_path, conn.src_var_name, + conn_comp_name, :input1, + conn.ignoreunits)) # add a connection between ConnectorComp and dst_component add_internal_param_conn!(md, InternalParameterConnection(conn_comp_name, :output, - conn.dst_comp_name, conn.dst_par_name, + conn.dst_comp_path, conn.dst_par_name, conn.ignoreunits)) # add a connection between ConnectorComp and the external backup data add_external_param_conn!(md, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) - src_comp_def = compdef(md, conn.src_comp_name) + src_comp_def = compdef(md, conn.src_comp_path) set_param!(md, conn_comp_name, :first, first_period(md, src_comp_def)) set_param!(md, conn_comp_name, :last, last_period(md, src_comp_def)) @@ -522,15 +587,15 @@ end # """ - dependencies(md::ModelDef, comp_name::Symbol) + dependencies(md::ModelDef, comp_path::ComponentPath) -Return the set of component names that `comp_name` in `md` depends one, i.e., +Return the set of component names that `comp_path` in `md` depends one, i.e., sources for which `comp_name` is the destination of an internal connection. """ -function dependencies(md::ModelDef, comp_name::Symbol) +function dependencies(md::ModelDef, comp_path::ComponentPath) conns = internal_param_conns(md) # For the purposes of the DAG, we don't treat dependencies on [t-1] as an ordering constraint - deps = Set(c.src_comp_name for c in conns if (c.dst_comp_name == comp_name && c.offset == 0)) + deps = Set(c.src_comp_path for c in conns if (c.dst_comp_path == comp_path && c.offset == 0)) return deps end @@ -541,19 +606,19 @@ Return a MetaGraph containing a directed (LightGraph) graph of the components of ModelDef `md`. Each vertex has a :name property with its component name. """ function comp_graph(md::ModelDef) - comp_names = collect(compkeys(md)) + comp_paths = [c.comp_path for c in compdefs(md)] graph = MetaDiGraph() - for comp_name in comp_names - add_vertex!(graph, :name, comp_name) + for comp_path in comp_paths + add_vertex!(graph, :path, comp_path) end - set_indexing_prop!(graph, :name) + set_indexing_prop!(graph, :path) - for comp_name in comp_names - for dep_name in dependencies(md, comp_name) - src = graph[dep_name, :name] - dst = graph[comp_name, :name] + for comp_path in comp_paths + for dep_path in dependencies(md, comp_path) + src = graph[dep_path, :path] + dst = graph[comp_path, :path] add_edge!(graph, src, dst) end end @@ -572,12 +637,12 @@ end Build a directed acyclic graph referencing the positions of the components in the OrderedDict of model `md`, tracing dependencies to create the DAG. Perform a topological sort on the graph for the given model and return a vector -of component names in the order that will ensure dependencies are processed +of component paths in the order that will ensure dependencies are processed prior to dependent components. """ function _topological_sort(md::ModelDef) graph = comp_graph(md) ordered = topological_sort_by_dfs(graph) - names = map(i -> graph[i, :name], ordered) - return names + paths = map(i -> graph[i, :path], ordered) + return paths end diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 60ea4dc08..d9c5f875d 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -257,76 +257,3 @@ macro defcomp(comp_name, ex) return esc(result) end - -""" - defmodel(model_name::Symbol, ex::Expr) - -Define a Mimi model. The following types of expressions are supported: - -1. `component(name)` # add comp to model -2. `dst_component.name = ex::Expr` # provide a value for a parameter -3. `src_component.name => dst_component.name` # connect a variable to a parameter -4. `index[name] = iterable-of-values` # define values for an index -""" -macro defmodel(model_name, ex) - @capture(ex, elements__) - - # @__MODULE__ is evaluated in calling module when macro is interpreted - # TBD: simplify using __module__ ? - result = :( - let calling_module = @__MODULE__, comp_mod_name = nothing - global $model_name = Model() - end - ) - - # helper function used in loop below - function addexpr(expr) - let_block = result.args[end].args - push!(let_block, expr) - end - - for elt in elements - offset = 0 - - if @capture(elt, component(comp_mod_name_.comp_name_) | component(comp_name_) | - component(comp_mod_name_.comp_name_, alias_) | component(comp_name_, alias_)) - - # set local copy of comp_mod_name to the stated or default component module - expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) - : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) - addexpr(expr) - - name = (alias === nothing ? comp_name : alias) - expr = :(add_comp!($model_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) - - # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross - elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || - @capture(elt, src_comp_.src_name_ => dst_comp_.dst_name_)) - if (arg !== nothing && (! @capture(arg, t - offset_) || offset <= 0)) - error("Subscripted connection source must have subscript [t - x] where x is an integer > 0") - end - - expr = :(Mimi.connect_param!($model_name, - $(QuoteNode(dst_comp)), $(QuoteNode(dst_name)), - $(QuoteNode(src_comp)), $(QuoteNode(src_name)), - offset=$offset)) - - elseif @capture(elt, index[idx_name_] = rhs_) - expr = :(Mimi.set_dimension!($model_name, $(QuoteNode(idx_name)), $rhs)) - - elseif @capture(elt, comp_name_.param_name_ = rhs_) - expr = :(Mimi.set_param!($model_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) - - else - # Pass through anything else to allow the user to define intermediate vars, etc. - @info "Passing through: $elt" - expr = elt - end - - addexpr(expr) - end - - # addexpr(:($model_name)) # return this or nothing? - addexpr(:(nothing)) - return esc(result) -end diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 5ad948866..b9c2c06e2 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -57,7 +57,7 @@ are all variations on `component(...)`, which adds a component to the composite. calling signature for `component()` processed herein is: `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; - exports::Union{Nothing,ExportsDef}, bindings::Union{Nothing,BindingsDef})` + exports::Union{Nothing,ExportsDict}, bindings::Union{Nothing,Vector{Binding}})` In this macro, the vector of symbols to export is expressed without the `:`, e.g., `exports=[var_1, var_2, param_1])`. The names must be variable or parameter names in diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl new file mode 100644 index 000000000..7d7bb4bc8 --- /dev/null +++ b/src/core/defmodel.jl @@ -0,0 +1,107 @@ +# +# @defmodel and supporting functions +# +using MacroTools + +""" +Target looks like this: + +# Test the calls the macro will produce +let calling_module = @__MODULE__ + global m = Model() + + ccname = :testcomp + ccid = ComponentId(calling_module, ccname) + comps = AbstractComponentDef[compdef(Comp1), compdef(Comp2), compdef(Comp3)] + + # TBD: need to implement this to create connections and default value + bindings = Binding[ + DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 + DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2)] + + exports = [ + DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + DatumReference(:par_2_2, Comp2) => :c2p2, + DatumReference(:var_3_1, Comp3) => :c3v1] + + m.md = md = ModelDef() + CompositeComponentDef(md, ccid, comps, bindings, exports) + + set_dimension!(m, :time, 2005:2020) + nothing +end +""" + +""" + defmodel(model_name::Symbol, ex::Expr) + +Define a Mimi model. The following types of expressions are supported: + +1. `component(name)` # add comp to model +2. `dst_component.name = ex::Expr` # provide a value for a parameter +3. `src_component.name => dst_component.name` # connect a variable to a parameter +4. `index[name] = iterable-of-values` # define values for an index +""" +macro defmodel(model_name, ex) + @capture(ex, elements__) + + # @__MODULE__ is evaluated in calling module when macro is interpreted + # TBD: simplify using __module__ ? + result = :( + let calling_module = @__MODULE__, comp_mod_name = nothing + global $model_name = Model() + end + ) + + # helper function used in loop below + function addexpr(expr) + let_block = result.args[end].args + push!(let_block, expr) + end + + for elt in elements + offset = 0 + + if @capture(elt, component(comp_mod_name_.comp_name_) | component(comp_name_) | + component(comp_mod_name_.comp_name_, alias_) | component(comp_name_, alias_)) + + # set local copy of comp_mod_name to the stated or default component module + expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) + : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) + addexpr(expr) + + name = (alias === nothing ? comp_name : alias) + expr = :(add_comp!($model_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) + + # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross + elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || + @capture(elt, src_comp_.src_name_ => dst_comp_.dst_name_)) + if (arg !== nothing && (! @capture(arg, t - offset_) || offset <= 0)) + error("Subscripted connection source must have subscript [t - x] where x is an integer > 0") + end + + expr = :(Mimi.connect_param!($model_name, + $(QuoteNode(dst_comp)), $(QuoteNode(dst_name)), + $(QuoteNode(src_comp)), $(QuoteNode(src_name)), + offset=$offset)) + + elseif @capture(elt, index[idx_name_] = rhs_) + expr = :(Mimi.set_dimension!($model_name, $(QuoteNode(idx_name)), $rhs)) + + elseif @capture(elt, comp_name_.param_name_ = rhs_) + expr = :(Mimi.set_param!($model_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) + + else + # Pass through anything else to allow the user to define intermediate vars, etc. + @info "Passing through: $elt" + expr = elt + end + + addexpr(expr) + end + + # addexpr(:($model_name)) # return this or nothing? + addexpr(:(nothing)) + return esc(result) +end diff --git a/src/core/defs.jl b/src/core/defs.jl index be50b12a2..52f83b270 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -20,7 +20,7 @@ end compdef(dr::AbstractDatumReference) = compdef(dr.root, dr.comp_path) -compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) +compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path, relative=false) # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] @@ -159,12 +159,10 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) # TBD: find and delete external_params associated with deleted component? Currently no record of this. - - # TBD: these probably need to use comp_path rather than symbols - ipc_filter = x -> x.src_comp_name != comp_name && x.dst_comp_name != comp_name + ipc_filter = x -> x.src_comp_path != comp_path && x.dst_comp_path != comp_path filter!(ipc_filter, ccd.internal_param_conns) - epc_filter = x -> x.comp_name != comp_name + epc_filter = x -> x.comp_path != comp_path filter!(epc_filter, ccd.external_param_conns) end @@ -448,19 +446,26 @@ function parameter(obj::AbstractCompositeComponentDef, name::Symbol) _parameter(obj, name) end - has_parameter(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.parameters, name) -function parameter_unit(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) - param = parameter(obj, comp_name, param_name) +function parameter_unit(obj::AbstractComponentDef, param_name::Symbol) + param = _parameter(obj, param_name) return param.unit end -function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) - param = parameter(obj, comp_name, param_name) +function parameter_dimensions(obj::AbstractComponentDef, param_name::Symbol) + param = _parameter(obj, param_name) return dim_names(param) end +function parameter_unit(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) + return parameter_unit(compdef(obj, comp_name), param_name) +end + +function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, param_name::Symbol) + return parameter_dimensions(compdef(obj, comp_name), param_name) +end + """ set_param!(m::ModelDef, comp_name::Symbol, name::Symbol, value, dims=nothing) @@ -574,7 +579,12 @@ end variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), var_name) -variable(md::ModelDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(md, comp_name), var_name) +variable(obj::AbstractCompositeComponentDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(obj, comp_name), var_name) + +function variable(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) + comp_def = find_comp(obj, comp_path, relative=false) + return variable(comp_def, var_name) +end variable(obj::VariableDefReference) = variable(compdef(obj), nameof(dr)) @@ -591,16 +601,27 @@ variable_names(md::AbstractModelDef, comp_name::Symbol) = variable_names(compdef variable_names(comp_def::ComponentDef) = [nameof(var) for var in variables(comp_def)] -function variable_unit(md::ModelDef, comp_name::Symbol, var_name::Symbol) - var = variable(md, comp_name, var_name) +function variable_unit(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) + var = variable(obj, comp_path, var_name) return var.unit end -function variable_dimensions(md::ModelDef, comp_name::Symbol, var_name::Symbol) - var = variable(md, comp_name, var_name) +function variable_dimensions(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) + var = variable(obj, comp_path, var_name) return dim_names(var) end +function variable_unit(obj::AbstractComponentDef, name::Symbol) + var = variable(obj, name) + return var.unit +end + +function variable_dimensions(obj::AbstractComponentDef, name::Symbol) + var = variable(obj, name) + return dim_names(var) +end + + # Add a variable to a ComponentDef. CompositeComponents have no vars of their own, # only references to vars in components contained within. function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) @@ -849,16 +870,16 @@ Optional boolean argument `reconnect` with default value `true` indicates whethe parameter connections should be maintained in the new component. """ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, - comp_name::Symbol=comp_id.comp_name; - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing, - reconnect::Bool=true) + comp_name::Symbol=comp_id.comp_name; + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing, + reconnect::Bool=true) if ! has_comp(obj, comp_name) error("Cannot replace '$comp_name'; component not found in model.") end - # Get original position if new before or after not specified + # Get original position if neither before nor after are specified if before === nothing && after === nothing comps = collect(compkeys(obj)) n = length(comps) @@ -897,7 +918,7 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end # Check outgoing variables - outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_name == comp_name, internal_param_conns(obj))) + outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_path == comp_name, internal_param_conns(obj))) old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) new_vars = new_comp.variables if !_compare_datum(new_vars, old_vars) @@ -935,18 +956,54 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end -function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) - if isempty(path) - return obj - end +function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) + if relative + if isempty(path) + return obj + end - name = path.names[1] - if has_comp(obj, name) - return find_comp(compdef(obj, name), ComponentPath(path.names[2:end])) + names = path.names + if has_comp(obj, names[1]) + return find_comp(compdef(obj, names[1]), ComponentPath(names[2:end])) + end + else + return find_comp(obj, rel_path(obj.comp_path, path)) # perform a relative search end return nothing end -find_comp(obj::AbstractComponentDef, name::Symbol) = find_comp(obj, ComponentPath(name)) +find_comp(obj::AbstractComponentDef, name::Symbol; relative=true) = find_comp(obj, ComponentPath(name), relative=relative) + +find_comp(obj::ComponentDef, path::ComponentPath; relative=true) = (isempty(path) ? obj : nothing) + +""" +Return the relative path of `descendant` if is within the path of composite `ancestor` or +or nothing otherwise. +""" +function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) + a_names = ancestor_path.names + d_names = descendant_path.names + + if ((a_len = length(a_names)) >= (d_len = length(d_names)) || d_names[1:a_len] != a_names) + return nothing + end + + return ComponentPath(d_names[a_len+1:end]) +end + +""" +Return whether component `descendant` is within the composite structure of `ancestor` or +any of its descendants. If the comp_paths check out, the node is located within the +structure to ensure that the component is really where it says it is. (Trust but verify!) +""" +function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) + a_path = ancestor.comp_path + d_path = descendant.comp_path + if (relpath = rel_path(a_path, d_path)) === nothing + return false + end + + return find_comp(ancestor, relpath) +end + -find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) diff --git a/src/core/model.jl b/src/core/model.jl index c0bc887c2..fb7f63d43 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -33,21 +33,21 @@ is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate add_connector_comps(m::Model) => md """ - connect_param!(m::Model, dst_comp_name::Symbol, dst_par_name::Symbol, src_comp_name::Symbol, + connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, src_comp_path::ComponentPath, src_var_name::Symbol, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) -Bind the parameter `dst_par_name` of one component `dst_comp_name` of model `md` -to a variable `src_var_name` in another component `src_comp_name` of the same model +Bind the parameter `dst_par_name` of one component `dst_comp_path` of model `md` +to a variable `src_var_name` in another component `src_comp_path` of the same model using `backup` to provide default values and the `ignoreunits` flag to indicate the need to check match units between the two. The `offset` argument indicates the offset between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -function connect_param!(m::Model, dst_comp_name::Symbol, dst_par_name::Symbol, - src_comp_name::Symbol, src_var_name::Symbol, +function connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, + src_comp_path::ComponentPath, src_var_name::Symbol, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) - connect_param!(m.md, dst_comp_name, dst_par_name, src_comp_name, src_var_name, backup; + connect_param!(m.md, dst_comp_path, dst_par_name, src_comp_path, src_var_name, backup; ignoreunits=ignoreunits, offset=offset) end @@ -68,6 +68,7 @@ function connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, S connect_param!(m.md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end +@delegate disconnect_param!(m::Model, comp_path::ComponentPath, param_name::Symbol) => md @delegate disconnect_param!(m::Model, comp_name::Symbol, param_name::Symbol) => md @delegate set_external_param!(m::Model, name::Symbol, value::ModelParameter) => md @@ -224,15 +225,9 @@ parameters(m::Model, comp_name::Symbol) = parameters(compdef(m, comp_name)) variable(m::Model, comp_name::Symbol, var_name::Symbol) = variable(compdef(m, comp_name), var_name) -function variable_unit(m::Model, comp_name::Symbol, var_name::Symbol) - var = variable(m, comp_id, var_name) - return unit(var) -end +@delegate variable_unit(m::Model, comp_path::ComponentPath, var_name::Symbol) => md -function variable_dimensions(m::Model, comp_name::Symbol, var_name::Symbol) - var = variable(m, comp_id, var_name) - return dim_names(var) -end +@delegate variable_dimensions(m::Model, comp_path::ComponentPath, var_name::Symbol) => md """ variables(m::Model, comp_name::Symbol) diff --git a/src/core/show.jl b/src/core/show.jl index 92c90e3e2..91e4bffb4 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -155,7 +155,7 @@ function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) name = (value === nothing ? "nothing" : nameof(value)) fields = deleteat!([fields...], pos) print(io, "\n") - print_indented(indent(io), "$field: $name") + print_indented(indent(io), "$field: $(typeof(value))($name)") end end diff --git a/src/core/types.jl b/src/core/types.jl index 9ca828a10..b5d36c979 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -7,6 +7,36 @@ abstract type MimiStruct end const AbstractMimiType = Union{MimiStruct, AbstractMimiClass} +# To identify components, @defcomp creates a variable with the name of +# the component whose value is an instance of this type. +struct ComponentId <: MimiStruct + module_name::Symbol + comp_name::Symbol +end + +# Identifies the path through multiple composites to a leaf component +# TBD: Could be just a tuple of Symbols since they are unique at each level. +struct ComponentPath <: MimiStruct + names::NTuple{N, Symbol} where N +end + +ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) + +ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) + +ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path1.names...)) + +ComponentPath(name::Symbol) = ComponentPath((name,)) + +ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) + +Base.isempty(obj::ComponentPath) = isempty(obj.names) + +# The equivalent of ".." in the file system. +Base.parent(path::ComponentPath) = ComponentPath(path.names[1:end-1]) + +ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) + # # 1. Types supporting parameterized Timestep and Clock objects # @@ -151,23 +181,24 @@ dim_names(obj::ScalarModelParameter) = [] abstract type AbstractConnection <: MimiStruct end struct InternalParameterConnection <: AbstractConnection - src_comp_name::Symbol + src_comp_path::ComponentPath src_var_name::Symbol - dst_comp_name::Symbol + dst_comp_path::ComponentPath dst_par_name::Symbol ignoreunits::Bool backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing offset::Int - function InternalParameterConnection(src_comp::Symbol, src_var::Symbol, dst_comp::Symbol, dst_par::Symbol, + function InternalParameterConnection(src_path::ComponentPath, src_var::Symbol, + dst_path::ComponentPath, dst_par::Symbol, ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) - self = new(src_comp, src_var, dst_comp, dst_par, ignoreunits, backup, offset) + self = new(src_path, src_var, dst_path, dst_par, ignoreunits, backup, offset) return self end end struct ExternalParameterConnection <: AbstractConnection - comp_name::Symbol + comp_path::ComponentPath param_name::Symbol # name of the parameter in the component external_param::Symbol # name of the parameter stored in external_params end @@ -176,36 +207,6 @@ end # 4. Types supporting structural definition of models and their components # -# To identify components, @defcomp creates a variable with the name of -# the component whose value is an instance of this type. -struct ComponentId <: MimiStruct - module_name::Symbol - comp_name::Symbol -end - -# Identifies the path through multiple composites to a leaf component -# TBD: Could be just a tuple of Symbols since they are unique at each level. -struct ComponentPath <: MimiStruct - names::NTuple{N, Symbol} where N -end - -ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) - -ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) - -ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path1.names...)) - -ComponentPath(name::Symbol) = ComponentPath((name,)) - -ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) - -Base.isempty(obj::ComponentPath) = isempty(obj.names) - -# The equivalent of ".." in the file system. -Base.parent(path::ComponentPath) = ComponentPath(path.names[1:end-1]) - -ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) - # Objects with a `name` attribute @class NamedObj <: MimiClass begin name::Symbol @@ -265,7 +266,7 @@ end NamedObj(self, name) self.comp_id = comp_id - self.comp_path = nothing # this is set in add_comp!() + self.comp_path = nothing # this is set in add_comp!() and ModelDef() self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() @@ -283,6 +284,7 @@ end end comp_id(obj::AbstractComponentDef) = obj.comp_id +pathof(obj::AbstractComponentDef) = obj.comp_path dim_dict(obj::AbstractComponentDef) = obj.dim_dict first_period(obj::AbstractComponentDef) = obj.first last_period(obj::AbstractComponentDef) = obj.last @@ -337,18 +339,6 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} end end -# Deprecated? -# # Create an empty composite compdef with all containers allocated but empty -# function CompositeComponentDef(self::Union{Nothing, AbstractCompositeComponentDef}=nothing) -# self = (self === nothing ? new() : self) - -# comp_id = ComponentId(@__MODULE__, gensym(nameof(typeof(self)))) -# comps = Vector{T where T <: AbstractComponentDef}() -# bindings = Vector{Binding}() -# exports = ExportsDict() -# return CompositeComponentDef(self, comp_id, comps, bindings, exports) -# end - # TBD: these should dynamically and recursively compute the lists internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns @@ -365,6 +355,8 @@ is_leaf(c::AbstractComponentDef) = true is_leaf(c::AbstractCompositeComponentDef) = false is_composite(c::AbstractComponentDef) = !is_leaf(c) +ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath(obj.comp_path, name) + @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType dirty::Bool @@ -513,6 +505,7 @@ end # These can be called on CompositeComponentInstances and ModelInstances compdef(obj::AbstractComponentInstance) = compdef(comp_id(obj)) +pathof(obj::AbstractComponentInstance) = obj.comp_path has_dim(obj::AbstractComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[name] first_period(obj::AbstractComponentInstance) = obj.first diff --git a/src/utils/graph.jl b/src/utils/graph.jl index ce4a61af1..27ae74ae6 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -13,9 +13,9 @@ function _show_conns(io, m, comp_name, which::Symbol) else for conn in conns if which == :incoming - println(io, " - $(conn.src_comp_name).$(conn.dst_par_name)") + println(io, " - $(conn.src_comp_path).$(conn.dst_par_name)") else - println(io, " - $(conn.dst_comp_name).$(conn.src_var_name)") + println(io, " - $(conn.dst_comp_path).$(conn.src_var_name)") end end end @@ -34,13 +34,13 @@ function show_conns(io::IO, m::Model) end end -function _filter_connections(conns::Vector{InternalParameterConnection}, comp_name::Symbol, which::Symbol) +function _filter_connections(conns::Vector{InternalParameterConnection}, comp_path::ComponentPath, which::Symbol) if which == :all - f = obj -> (obj.src_comp_name == comp_name || obj.dst_comp_name == comp_name) + f = obj -> (obj.src_comp_path == comp_path || obj.dst_comp_path == comp_path) elseif which == :incoming - f = obj -> obj.dst_comp_name == comp_name + f = obj -> obj.dst_comp_path == comp_path elseif which == :outgoing - f = obj -> obj.src_comp_name == comp_name + f = obj -> obj.src_comp_path == comp_path else error("Invalid parameter for the 'which' argument; must be 'all' or 'incoming' or 'outgoing'.") end @@ -50,22 +50,22 @@ end function get_connections(m::Model, ci::ComponentInstance, which::Symbol) if is_leaf(ci) - return get_connections(m, nameof(ci.comp), which) + return get_connections(m, pathof(ci), which) end conns = [] for ci in components(cci) - append!(conns, get_connections(m, ci, which)) + append!(conns, get_connections(m, pathof(ci), which)) end return conns end -function get_connections(m::Model, comp_name::Symbol, which::Symbol) +function get_connections(m::Model, comp_path::ComponentPath, which::Symbol) md = modeldef(m) - return _filter_connections(internal_param_conns(md), comp_name, which) + return _filter_connections(internal_param_conns(md), comp_path, which) end -function get_connections(mi::ModelInstance, comp_name::Symbol, which::Symbol) +function get_connections(mi::ModelInstance, comp_path::ComponentPath, which::Symbol) md = modeldef(mi) - return _filter_connections(internal_param_conns(md), comp_name, which) + return _filter_connections(internal_param_conns(md), comp_path, which) end diff --git a/test/test_composite.jl b/test/test_composite.jl index a4726af9c..3107f05d4 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -41,13 +41,12 @@ end end end + # Test the calls the macro will produce let calling_module = @__MODULE__ # calling_module = TestComposite global m = Model() - ccname = :testcomp - ccid = ComponentId(calling_module, ccname) comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] # TBD: need to implement this to create connections and default value @@ -63,7 +62,7 @@ let calling_module = @__MODULE__ # DatumReference(:var_3_1, Comp3) => :c3v1 # ] - CompositeComponentDef(m.md, ccid) # , comps, bindings, exports) + # CompositeComponentDef(m.md, ccid) # , comps, bindings, exports) set_dimension!(m, :time, 2005:2020) @@ -85,8 +84,8 @@ connect_param!(md, :Comp2, :par_2_1, :Comp1, :var_1_1) connect_param!(md, :Comp2, :par_2_2, :Comp1, :var_1_1) connect_param!(md, :Comp3, :par_3_1, :Comp2, :var_2_1) -build(m) -run(m) +# build(m) +# run(m) end # module From 576f3e106088ca8a8f5ea7115a05cd7627cdfab0 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 15 Feb 2019 09:44:59 -0800 Subject: [PATCH 36/81] Created a file to think about restructuring the "Def" space into "registered templates" and actual model definition. --- src/core/_preliminary.jl | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/core/_preliminary.jl diff --git a/src/core/_preliminary.jl b/src/core/_preliminary.jl new file mode 100644 index 000000000..7590fd9de --- /dev/null +++ b/src/core/_preliminary.jl @@ -0,0 +1,42 @@ +# Preliminary file to think through David's suggestion of splitting defs between "registered" +# readonly "templates" and user-constructed models so it's clear which functions operate on +# templates vs defs within a model. + +@class ComponentDef <: NamedObj begin + comp_id::Union{Nothing, ComponentId} + variables::OrderedDict{Symbol, VariableDef} + parameters::OrderedDict{Symbol, ParameterDef} + dim_names::Set{Symbol} +end + +@class CompositeComponentDef <: ComponentDef begin + comps_dict::OrderedDict{Symbol, AbstractComponentDef} + exports::ExportsDict + internal_param_conns::Vector{InternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} +end + +# Define these for building out a ModelDef, which reference the +# central definitions using the classes above. + +@class mutable ModelComponentDef <: NamedObj begin + comp_id::ComponentId # references registered component def + comp_path::Union{Nothing, ComponentPath} + dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + is_uniform::Bool +end + +@class mutable ModelCompositeComponentDef <: ModelComponentDef begin + comps_dict::OrderedDict{Symbol, AbstractModelComponentDef} + bindings::Vector{Binding} + exports::ExportsDict + external_param_conns::Vector{ExternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} + + # Names of external params that the ConnectorComps will use as their :input2 parameters. + backups::Vector{Symbol} + + sorted_comps::Union{Nothing, Vector{Symbol}} +end From 46ae936a2b60d120af4ee95f5dc7404194c543d3 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 20 Feb 2019 10:41:36 -0800 Subject: [PATCH 37/81] Pass line numbers through for components' run_timestep and init functions --- src/core/defcomp.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index d9c5f875d..2681b026c 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -159,9 +159,11 @@ macro defcomp(comp_name, ex) if @capture(elt, function fname_(args__) body__ end) if fname == :run_timestep + body = elt.args[2].args # replace captured body with this, which includes line numbers expr = _generate_run_func(comp_name, nameof(__module__), args, body) elseif fname == :init + body = elt.args[2].args # as above expr = _generate_init_func(comp_name, nameof(__module__), args, body) else error("@defcomp can contain only these functions: init(p, v, d) and run_timestep(p, v, d, t)") From 1328710048a74f61298feb96896436879966b60d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 26 Feb 2019 11:05:30 -0800 Subject: [PATCH 38/81] WIP check-in - working through tests. --- src/Mimi.jl | 2 +- src/core/build.jl | 6 +- src/core/connections.jl | 19 ++- src/core/defmodel.jl | 3 +- src/core/defs.jl | 158 ++++++++++++------ src/core/model.jl | 31 +++- src/core/references.jl | 9 +- src/core/show.jl | 2 +- src/core/types.jl | 24 +-- src/utils/graph.jl | 5 + test/test_model_structure.jl | 11 +- test/test_model_structure_variabletimestep.jl | 9 +- test/test_references.jl | 2 +- 13 files changed, 188 insertions(+), 93 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index 7479a9fd4..f0076ab69 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -66,7 +66,7 @@ include("core/show.jl") #include("explorer/explore.jl") #include("mcs/mcs.jl") #include("utils/getdataframe.jl") -#include("utils/graph.jl") +include("utils/graph.jl") #include("utils/lint_helper.jl") #include("utils/misc.jl") #include("utils/plotting.jl") diff --git a/src/core/build.jl b/src/core/build.jl index 5bb978a29..b54a6747a 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -201,9 +201,9 @@ function _build(comp_def::ComponentDef, end function _build(comp_def::AbstractCompositeComponentDef, - var_dict::Dict{ComponentPath, Any}, - par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, - time_bounds::Tuple{Int, Int}) + var_dict::Dict{ComponentPath, Any}, + par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, + time_bounds::Tuple{Int, Int}) # @info "_build composite $(comp_def.comp_id)" # @info " var_dict $(var_dict)" # @info " par_dict $(par_dict)" diff --git a/src/core/connections.jl b/src/core/connections.jl index 4418c2a17..6479396ce 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -128,7 +128,8 @@ parameter should only be calculated for the second timestep and beyond. function connect_param!(obj::AbstractCompositeComponentDef, dst_comp_path::ComponentPath, dst_par_name::Symbol, src_comp_path::ComponentPath, src_var_name::Symbol, - backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + backup::Union{Nothing, Array}=nothing; + ignoreunits::Bool=false, offset::Int=0) # remove any existing connections for this dst parameter disconnect_param!(obj, dst_comp_path, dst_par_name) # calls dirty!() @@ -561,22 +562,22 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) conn.ignoreunits)) # add a connection between ConnectorComp and dst_component - add_internal_param_conn!(md, InternalParameterConnection(conn_comp_name, :output, - conn.dst_comp_path, conn.dst_par_name, - conn.ignoreunits)) + add_internal_param_conn!(obj, InternalParameterConnection(conn_comp_name, :output, + conn.dst_comp_path, conn.dst_par_name, + conn.ignoreunits)) # add a connection between ConnectorComp and the external backup data - add_external_param_conn!(md, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) + add_external_param_conn!(obj, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) - src_comp_def = compdef(md, conn.src_comp_path) - set_param!(md, conn_comp_name, :first, first_period(md, src_comp_def)) - set_param!(md, conn_comp_name, :last, last_period(md, src_comp_def)) + src_comp_def = compdef(obj, conn.src_comp_path) + set_param!(obj, conn_comp_name, :first, first_period(obj, src_comp_def)) + set_param!(obj, conn_comp_name, :last, last_period(obj, src_comp_def)) end end # Save the sorted component order for processing - # md.sorted_comps = _topological_sort(md) + # obj.sorted_comps = _topological_sort(obj) return nothing end diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index 7d7bb4bc8..2be07bf8e 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -72,7 +72,8 @@ macro defmodel(model_name, ex) addexpr(expr) name = (alias === nothing ? comp_name : alias) - expr = :(add_comp!($model_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) + expr = :(add_comp!($model_name, + Mimi.ComponentId(comp_mod_name, $(QuoteNode(comp_name))), $(QuoteNode(name)))) # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || diff --git a/src/core/defs.jl b/src/core/defs.jl index 52f83b270..a2eecee9a 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -38,7 +38,6 @@ compname(obj::AbstractComponentDef) = compname(obj.comp_id) compnames() = map(compname, compdefs()) - # Access a subcomponent as comp[:name] Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict[name] @@ -55,6 +54,24 @@ function comp_path!(parent::AbstractCompositeComponentDef, child::AbstractCompon child.comp_path = ComponentPath(parent.comp_path, child.name) end +""" + comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + +Convert a string describing a path from a node to a ComponentPath. +""" +function comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + # empty path means just select the node's path + isempty(path) && return node.comp_path + + elts = split(path, "/") + + if elts[1] == "" + root = get_root(node) + elts[1] = String(nameof(root)) + end + return ComponentPath([Symbol(elt) for elt in elts]) +end + dirty(md::ModelDef) = md.dirty function dirty!(obj::AbstractComponentDef) @@ -78,24 +95,24 @@ end first_period(comp_def::ComponentDef) = comp_def.first last_period(comp_def::ComponentDef) = comp_def.last -function first_period(comp_def::CompositeComponentDef) - values = filter(!isnothing, [first_period(c) for c in comp_def]) +function first_period(comp::AbstractCompositeComponentDef) + values = filter(!isnothing, [first_period(c) for c in compdefs(comp)]) return length(values) > 0 ? min(values...) : nothing end -function last_period(comp_def::CompositeComponentDef) - values = filter(!isnothing, [last_period(c) for c in comp_def]) +function last_period(comp::AbstractCompositeComponentDef) + values = filter(!isnothing, [last_period(c) for c in compdefs(comp)]) return length(values) > 0 ? max(values...) : nothing end -function first_period(md::ModelDef, comp_def::AbstractComponentDef) +function first_period(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) period = first_period(comp_def) - return period === nothing ? time_labels(md)[1] : period + return period === nothing ? time_labels(obj)[1] : period end -function last_period(md::ModelDef, comp_def::AbstractComponentDef) +function last_period(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) period = last_period(comp_def) - return period === nothing ? time_labels(md)[end] : period + return period === nothing ? time_labels(obj)[end] : period end compname(dr::AbstractDatumReference) = dr.comp_path.names[end] @@ -108,6 +125,7 @@ is_variable(dr::VariableDefReference) = has_variable(compdef(dr), nameof(dr)) is_parameter(dr::ParameterDefReference) = has_parameter(compdef(dr), nameof(dr)) number_type(md::ModelDef) = md.number_type +number_type(obj::AbstractCompositeComponentDef) = number_type(get_root(obj)) # TBD: should be numcomps() numcomponents(obj::AbstractComponentDef) = 0 # no sub-components @@ -200,13 +218,13 @@ end # # TBD: should these be defined as methods of CompositeComponentDef? # -function step_size(md::ModelDef) - keys::Vector{Int} = time_labels(md) +function step_size(obj::AbstractCompositeComponentDef) + keys::Vector{Int} = time_labels(obj) return step_size(keys) end -function first_and_step(md::ModelDef) - keys::Vector{Int} = time_labels(md) # labels are the first times of the model runs +function first_and_step(obj::AbstractCompositeComponentDef) + keys::Vector{Int} = time_labels(obj) # labels are the first times of the model runs return first_and_step(keys) end @@ -216,8 +234,8 @@ end first_and_last(obj::AbstractComponentDef) = (obj.first, obj.last) -function time_labels(md::ModelDef) - keys::Vector{Int} = dim_keys(md, :time) +function time_labels(obj::AbstractCompositeComponentDef) + keys::Vector{Int} = dim_keys(obj, :time) return keys end @@ -240,14 +258,14 @@ function check_parameter_dimensions(md::ModelDef, value::AbstractArray, dims::Ve end # TBD: is this needed for composites? -function datum_size(md::ModelDef, comp_def::ComponentDef, datum_name::Symbol) +function datum_size(obj::AbstractCompositeComponentDef, comp_def::ComponentDef, datum_name::Symbol) dims = dim_names(comp_def, datum_name) if dims[1] == :time - time_length = getspan(md, comp_def)[1] + time_length = getspan(obj, comp_def)[1] rest_dims = filter(x->x!=:time, dims) - datum_size = (time_length, dim_counts(md, rest_dims)...,) + datum_size = (time_length, dim_counts(obj, rest_dims)...,) else - datum_size = (dim_counts(md, dims)...,) + datum_size = (dim_counts(obj, dims)...,) end return datum_size end @@ -261,14 +279,14 @@ set_uniform!(obj::AbstractCompositeComponentDef, value::Bool) = (obj.is_uniform dimension(obj::AbstractCompositeComponentDef, name::Symbol) = obj.dim_dict[name] -dim_names(md::ModelDef, dims::Vector{Symbol}) = [dimension(md, dim) for dim in dims] +dim_names(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [dimension(obj, dim) for dim in dims] -dim_count_dict(md::ModelDef) = Dict([name => length(value) for (name, value) in dim_dict(md)]) -dim_counts(md::ModelDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(md, dims)] -dim_count(md::ModelDef, name::Symbol) = length(dimension(md, name)) +dim_count_dict(obj::AbstractCompositeComponentDef) = Dict([name => length(value) for (name, value) in dim_dict(obj)]) +dim_counts(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(obj, dims)] +dim_count(obj::AbstractCompositeComponentDef, name::Symbol) = length(dimension(obj, name)) -dim_keys(md::ModelDef, name::Symbol) = collect(keys(dimension(md, name))) -dim_values(md::ModelDef, name::Symbol) = collect(values(dimension(md, name))) +dim_keys(obj::AbstractCompositeComponentDef, name::Symbol) = collect(keys(dimension(obj, name))) +dim_values(obj::AbstractCompositeComponentDef, name::Symbol) = collect(values(dimension(obj, name))) # For debugging only function _show_run_period(obj::AbstractComponentDef, first, last) @@ -318,9 +336,9 @@ function set_run_period!(obj::AbstractComponentDef, first, last) end """ - set_dimension!(md::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) -Set the values of `md` dimension `name` to integers 1 through `count`, if `keys` is +Set the values of `ccd` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. """ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) @@ -337,7 +355,7 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: return set_dimension!(ccd, name, Dimension(keys)) end -function set_dimension!(obj::AbstractCompositeComponentDef, name::Symbol, dim::Dimension) +function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) dirty!(obj) obj.dim_dict[name] = dim end @@ -467,30 +485,43 @@ function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, para end """ - set_param!(m::ModelDef, comp_name::Symbol, name::Symbol, value, dims=nothing) + set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) + +Set a parameter for a component with the given relative path (as a string), in which "/x" means the +component with name `:x` beneath the root of the hierarchy in which `obj` is found. If the path does +not begin with "/", it is treated as relative to `obj`. +""" +function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) + cp = comp_path(obj, path) + comp = find_comp(obj, cp, relative=false) + set_param!(comp.parent, nameof(comp), param_name, value, dims) +end + +""" + set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, name::Symbol, value, dims=nothing) -Set the parameter `name` of a component `comp_name` in a model `m` to a given `value`. The +Set the parameter `name` of a component `comp_name` in a composite `obj` to a given `value`. The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ -function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) +function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) # perform possible dimension and labels checks if value isa NamedArray dims = dimnames(value) end if dims !== nothing - check_parameter_dimensions(md, value, dims, param_name) + check_parameter_dimensions(obj, value, dims, param_name) end - comp_param_dims = parameter_dimensions(md, comp_name, param_name) + comp_param_dims = parameter_dimensions(obj, comp_name, param_name) num_dims = length(comp_param_dims) - comp_def = compdef(md, comp_name) + comp_def = compdef(obj, comp_name) param = parameter(comp_def, param_name) data_type = param.datatype - dtype = data_type == Number ? number_type(md) : data_type + dtype = data_type == Number ? number_type(obj) : data_type if length(comp_param_dims) > 0 # convert the number type and, if NamedArray, convert to Array @@ -507,13 +538,13 @@ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, values = value else # Want to use the first from the comp_def if it has it, if not use ModelDef - first = first_period(md, comp_def) + first = first_period(obj, comp_def) - if isuniform(md) - stepsize = step_size(md) + if isuniform(obj) + stepsize = step_size(obj) values = TimestepArray{FixedTimestep{first, stepsize}, T, num_dims}(value) else - times = time_labels(md) + times = time_labels(obj) #use the first from the comp_def first_index = findfirst(isequal(first), times) values = TimestepArray{VariableTimestep{(times[first_index:end]...,)}, T, num_dims}(value) @@ -523,15 +554,15 @@ function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, values = value end - set_external_array_param!(md, param_name, values, comp_param_dims) + set_external_array_param!(obj, param_name, values, comp_param_dims) else # scalar parameter case value = convert(dtype, value) - set_external_scalar_param!(md, param_name, value) + set_external_scalar_param!(obj, param_name, value) end # connect_param! calls dirty! so we don't have to - connect_param!(md, comp_name, param_name, param_name) + connect_param!(obj, comp_name, param_name, param_name) nothing end @@ -591,14 +622,13 @@ variable(obj::VariableDefReference) = variable(compdef(obj), nameof(dr)) has_variable(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.variables, name) """ - variable_names(md::ModelDef, comp_name::Symbol) + variable_names(md::AbstractCompositeComponentDef, comp_name::Symbol) Return a list of all variable names for a given component `comp_name` in a model def `md`. """ -# TBD: why isn't this a of ComponentDef? -variable_names(md::AbstractModelDef, comp_name::Symbol) = variable_names(compdef(md, comp_name)) +variable_names(obj::AbstractCompositeComponentDef, comp_name::Symbol) = variable_names(compdef(obj, comp_name)) -variable_names(comp_def::ComponentDef) = [nameof(var) for var in variables(comp_def)] +variable_names(comp_def::AbstractComponentDef) = [nameof(var) for var in variables(comp_def)] function variable_unit(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) @@ -674,10 +704,10 @@ function _append_comp!(obj::AbstractCompositeComponentDef, comp_name::Symbol, co obj.comps_dict[comp_name] = comp_def end -function _add_anonymous_dims!(md::ModelDef, comp_def::AbstractComponentDef) +function _add_anonymous_dims!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) for (name, dim) in filter(pair -> pair[2] !== nothing, comp_def.dim_dict) # @info "Setting dimension $name to $dim" - set_dimension!(md, name, dim) + set_dimension!(obj, name, dim) end end @@ -758,6 +788,28 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom nothing end +""" +Return True if time Dimension `outer` contains `inner`. +""" +function time_contains(outer::Dimension, inner::Dimension) + outer_idx = keys(outer) + inner_idx = keys(inner) + + return outer_idx[1] <= inner_idx[1] && outer_idx[end] >= inner_idx[end] +end + +""" +Propagate a time dimension down through the comp def tree. +""" +function _propagate_time(obj::AbstractComponentDef, t::Dimension) + set_dimension!(obj, :time, t) + + if is_composite(obj) + for c in compdefs(obj) + _propagate_time(c, t) + end + end +end """ add_comp!(obj::AbstractCompositeComponentDef, comp_def::ComponentDef; @@ -795,7 +847,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone # check that a time dimension has been set if ! has_dim(obj, :time) - error("Cannot add component to model without first setting time dimension.") + error("Cannot add component to composite without first setting time dimension.") end # check that first and last are within the model's time index range @@ -818,6 +870,10 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot add two components of the same name ($comp_name)") end + if has_dim(obj, :time) + _propagate_time(comp_def, dimension(obj, :time)) + end + # Copy the original so we don't step on other uses of this comp comp_def = deepcopy(comp_def) comp_def.name = comp_name @@ -833,7 +889,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone set_param!(obj, comp_name, nameof(param), param.default) end end - + # Return the comp since it's a copy of what was passed in return comp_def end @@ -976,6 +1032,8 @@ find_comp(obj::AbstractComponentDef, name::Symbol; relative=true) = find_comp(ob find_comp(obj::ComponentDef, path::ComponentPath; relative=true) = (isempty(path) ? obj : nothing) +find_comp(obj::AbstractCompositeComponentDef, path::AbstractString; relative=true) = find_comp(obj, comp_path(obj, path), relative=relative) + """ Return the relative path of `descendant` if is within the path of composite `ancestor` or or nothing otherwise. diff --git a/src/core/model.jl b/src/core/model.jl index fb7f63d43..40194afb0 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -43,13 +43,17 @@ to check match units between the two. The `offset` argument indicates the offse between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -function connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, - src_comp_path::ComponentPath, src_var_name::Symbol, - backup::Union{Nothing, Array}=nothing; - ignoreunits::Bool=false, offset::Int=0) - connect_param!(m.md, dst_comp_path, dst_par_name, src_comp_path, src_var_name, backup; - ignoreunits=ignoreunits, offset=offset) -end +@delegate connect_param!(m::Model, + dst_comp_path::ComponentPath, dst_par_name::Symbol, + src_comp_path::ComponentPath, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; + ignoreunits::Bool=false, offset::Int=0) => md + +@delegate connect_param!(m::Model, + dst_comp_name::Symbol, dst_par_name::Symbol, + src_comp_name::Symbol, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; + ignoreunits::Bool=false, offset::Int=0) => md """ connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, backup::Array; ignoreunits::Bool=false) @@ -143,6 +147,8 @@ function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id return ComponentReference(m, comp_name) end +@delegate ComponentReference(m::Model, name::Symbol) => md + """ components(m::Model) @@ -268,7 +274,16 @@ The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ -@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md +@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md + +""" + set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) + +Set a parameter for a component with the given relative path (as a string), in which "/x" means the +component with name `:x` beneath `m.md`. If the path does not begin with "/", it is treated as +relative to `m.md`, which at the top of the hierarchy, produces the same result as starting with "/". +""" +@delegate set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) => md """ run(m::Model) diff --git a/src/core/references.jl b/src/core/references.jl index 9172b22d6..50e6c5874 100644 --- a/src/core/references.jl +++ b/src/core/references.jl @@ -44,13 +44,18 @@ function Base.getindex(comp_ref::ComponentReference, var_name::Symbol) VariableReference(comp_ref, var_name) end +function _same_composite(ref1::AbstractComponentReference, ref2::AbstractComponentReference) + # @info "same_composite($(ref1.comp_path), $(ref2.comp_path))" + return ref1.comp_path.names[1] == ref2.comp_path.names[1] +end + """ Base.setindex!(comp_ref::ComponentReference, var_ref::VariableReference, var_name::Symbol) Connect two components as `comp_ref[var_name] = var_ref`. """ function Base.setindex!(comp_ref::ComponentReference, var_ref::VariableReference, var_name::Symbol) - same_composite(comp_ref, var_ref)|| error("Can't connect variables defined in different composite trees") + _same_composite(comp_ref, var_ref)|| error("Can't connect variables defined in different composite trees") - connect_param!(comp_ref.parent, comp_ref.comp_name, var_name, var_ref.comp_name, var_ref.var_name) + connect_param!(comp_ref.parent, comp_ref.comp_path, var_name, var_ref.comp_path, var_ref.var_name) end diff --git a/src/core/show.jl b/src/core/show.jl index 91e4bffb4..3e8820c16 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -144,7 +144,7 @@ end function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) # Don't print parent or root since these create circular references - print(io, nameof(typeof(obj))) + print(io, nameof(typeof(obj)), " id:", objectid(obj)) fields = fieldnames(typeof(obj)) diff --git a/src/core/types.jl b/src/core/types.jl index b5d36c979..d499d73dc 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -250,7 +250,7 @@ end is_uniform::Bool # Store a reference to the AbstractCompositeComponent that contains this comp def. - # That type isn't defined yet, so we declare Any here. + # That type is defined later, so we declare Any here. parent::Union{Nothing, Any} function ComponentDef(self::ComponentDef, comp_id::Nothing) @@ -260,11 +260,14 @@ end # ComponentDefs are created "empty". Elements are subsequently added. function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; name::Union{Nothing, Symbol}=nothing) - if name === nothing - name = (comp_id === nothing ? gensym(nameof(typeof(self))) : comp_id.comp_name) + if comp_id === nothing + # ModelDefs are anonymous, but since they're gensym'd, they can claim the Mimi package + comp_id = ComponentId(Mimi, name === nothing ? gensym(nameof(typeof(self))) : name) end + name = (name === nothing ? comp_id.comp_name : name) NamedObj(self, name) + self.comp_id = comp_id self.comp_path = nothing # this is set in add_comp!() and ModelDef() self.variables = OrderedDict{Symbol, VariableDef}() @@ -365,7 +368,7 @@ ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath( self = new() CompositeComponentDef(self) # call super's initializer self.comp_path = ComponentPath(self.name) - return ModelDef(self, number_type, false) + return ModelDef(self, number_type, false) # call @class-generated method end end @@ -450,7 +453,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - + self.comp_id = comp_id = comp_def.comp_id self.comp_path = comp_def.comp_path self.comp_name = name @@ -472,10 +475,11 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro return nothing end - func_name = Symbol("$(name)_$(self.comp_id.comp_name)") + func_name = Symbol("$(name)_$(nameof(comp_module))_$(self.comp_id.comp_name)") try Base.eval(comp_module, func_name) catch err + # @info "Eval of $func_name in module $comp_module failed" nothing end end @@ -659,12 +663,12 @@ end comp_path::ComponentPath end +function ComponentReference(parent::AbstractComponentDef, name::Symbol) + return ComponentReference(parent, ComponentPath(parent.comp_path, name)) +end + # A container for a variable within a component, to improve connect_param! aesthetics, # by supporting subscripting notation via getindex & setindex . @class VariableReference <: ComponentReference begin var_name::Symbol end - -function same_composite(ref1::AbstractComponentReference, ref2::AbstractComponentReference) - return ref1.comp_path[1] == ref2.comp_path[1] -end diff --git a/src/utils/graph.jl b/src/utils/graph.jl index 27ae74ae6..6227aa9c8 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -65,6 +65,11 @@ function get_connections(m::Model, comp_path::ComponentPath, which::Symbol) return _filter_connections(internal_param_conns(md), comp_path, which) end +function get_connections(m::Model, comp_name::Symbol, which::Symbol) + comp = compdef(m, comp_name) + get_connections(m, comp.comp_path, which) +end + function get_connections(mi::ModelInstance, comp_path::ComponentPath, which::Symbol) md = modeldef(mi) return _filter_connections(internal_param_conns(md), comp_path, which) diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index af6b7df21..6e107241d 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -8,7 +8,7 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, - modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs + modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs, comp_path reset_compdefs() @@ -58,7 +58,9 @@ connect_param!(m, :A, :parA, :C, :varC) unconn = unconnected_params(m) @test length(unconn) == 1 -@test unconn[1] == (:C, :parC) + +c = compdef(m, :C) +@test unconn[1] == (c.comp_path, :parC) connect_param!(m, :C => :parC, :B => :varB) @@ -67,10 +69,11 @@ connect_param!(m, :C => :parC, :B => :varB) @test numcomponents(m.md) == 3 @test length(internal_param_conns(m)) == 2 -@test get_connections(m, :A, :incoming)[1].src_comp_name == :C +c = compdef(m, :C) +@test get_connections(m, :A, :incoming)[1].src_comp_path == c.comp_path @test length(get_connections(m, :B, :incoming)) == 0 -@test get_connections(m, :B, :outgoing)[1].dst_comp_name == :C +@test get_connections(m, :B, :outgoing)[1].dst_comp_path == c.comp_path @test length(get_connections(m, :A, :all)) == 1 diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 9fe86c618..31572e62d 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -64,7 +64,8 @@ connect_param!(m, :A, :parA, :C, :varC) unconn = unconnected_params(m) @test length(unconn) == 1 -@test unconn[1] == (:C, :parC) +c = compdef(m, :C) +@test unconn[1] == (c.comp_path, :parC) connect_param!(m, :C => :parC, :B => :varB) @@ -73,10 +74,12 @@ connect_param!(m, :C => :parC, :B => :varB) @test numcomponents(m.md) == 3 @test length(internal_param_conns(m)) == 2 -@test get_connections(m, :A, :incoming)[1].src_comp_name == :C +c = compdef(m, :C) +@test get_connections(m, :A, :incoming)[1].src_comp_path == c.comp_path @test length(get_connections(m, :B, :incoming)) == 0 -@test get_connections(m, :B, :outgoing)[1].dst_comp_name == :C +c = compdef(m, :C) +@test get_connections(m, :B, :outgoing)[1].dst_comp_path == c.comp_path @test length(get_connections(m, :A, :all)) == 1 diff --git a/test/test_references.jl b/test/test_references.jl index ec22af634..7bc3bab46 100644 --- a/test/test_references.jl +++ b/test/test_references.jl @@ -28,7 +28,7 @@ end @defmodel m begin - index[time] = [1] + index[time] = [1, 2] component(Foo) component(Bar) From be1d2aae1d4487e6490c38a3560c00ff5a35c437 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 28 Feb 2019 13:02:15 -0800 Subject: [PATCH 39/81] WIP - Numerous changes to get tests working --- src/Mimi.jl | 12 +-- src/core/build.jl | 40 +++++++--- src/core/connections.jl | 14 ++-- src/core/defs.jl | 110 ++++++++++++++++---------- src/core/instances.jl | 7 +- src/core/model.jl | 2 +- src/core/references.jl | 10 +-- src/core/time.jl | 2 +- src/core/types.jl | 12 +-- src/utils/misc.jl | 10 +-- test/test_components.jl | 10 +-- test/test_composite.jl | 83 ++++++++++--------- test/test_connectorcomp.jl | 2 +- test/test_replace_comp.jl | 17 ++-- test/test_tools.jl | 12 +-- test/test_variables_model_instance.jl | 12 +-- 16 files changed, 205 insertions(+), 150 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index f0076ab69..c3b51e923 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -63,13 +63,13 @@ include("core/model.jl") include("core/show.jl") # For debugging composites we don't need these -#include("explorer/explore.jl") -#include("mcs/mcs.jl") -#include("utils/getdataframe.jl") +include("explorer/explore.jl") +include("mcs/mcs.jl") +include("utils/getdataframe.jl") include("utils/graph.jl") -#include("utils/lint_helper.jl") -#include("utils/misc.jl") -#include("utils/plotting.jl") +include("utils/lint_helper.jl") +include("utils/misc.jl") +include("utils/plotting.jl") """ load_comps(dirname::String="./components") diff --git a/src/core/build.jl b/src/core/build.jl index b54a6747a..5d7a7f54e 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -73,7 +73,7 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) return ComponentInstanceVariables(names, types, values, paths) end -# Create ComponentInstanceVariables for a composite component from the list of exported vars +# TBD: Create ComponentInstanceVariables for a composite component from the list of exported vars function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dict::Dict{ComponentPath, Any}) names = Symbol[] values = Any[] @@ -110,8 +110,7 @@ function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dic end function _instantiate_vars(comp_def::ComponentDef, md::ModelDef, var_dict::Dict{ComponentPath, Any}) - var_dict[comp_def.comp_path] = v = _instantiate_component_vars(md, comp_def) - # @info "_instantiate_vars leaf $comp_name: $v" + var_dict[comp_def.comp_path] = _instantiate_component_vars(md, comp_def) end # Creates the top-level vars for the model @@ -127,9 +126,8 @@ function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef # @info "_instantiate_vars composite $comp_path" for cd in compdefs(comp_def) _instantiate_vars(cd, md, var_dict) - end - var_dict[comp_path] = v = _combine_exported_vars(comp_def, var_dict) - # @info "composite vars for $comp_path: $v " + end + var_dict[comp_path] = _combine_exported_vars(comp_def, var_dict) end # Do nothing if called on a leaf component @@ -163,10 +161,11 @@ function _collect_params(comp_def::AbstractCompositeComponentDef, # Make the external parameter connections for the hidden ConnectorComps. # Connect each :input2 to its associated backup value. for (i, backup) in enumerate(comp_def.backups) - conn_comp_name = connector_comp_name(i) + conn_comp = compdef(comp_def, connector_comp_name(i)) + conn_path = conn_comp.comp_path + param = external_param(comp_def, backup) - par_dict[(conn_comp_name, :input2)] = (param isa ScalarModelParameter ? param : value(param)) - # @info "backup: $conn_comp_name $param" + par_dict[(conn_path, :input2)] = (param isa ScalarModelParameter ? param : value(param)) end end @@ -212,7 +211,29 @@ function _build(comp_def::AbstractCompositeComponentDef, return CompositeComponentInstance(comps, comp_def, time_bounds) end +# """ +# Perform a depth-first search on components, exporting vars and params up +# through each composite level. +# """ +# function _propagate_exports(obj::AbstractComponentDef) +# # nothing to do for leaf components +# is_leaf(obj) && return + +# empty!(obj.exports) # start fresh + +# for comp in compdefs(obj) +# _propagate_exports(comp) + +# for (export_name, datum_ref) in comp.exports +# if datum_ref isa ParameterDefReference +# else +# end +# end +# end +# end + function _build(md::ModelDef) + # _propagate_exports(md) add_connector_comps(md) # check if all parameters are set @@ -236,7 +257,6 @@ function _build(md::ModelDef) ci = _build(md, var_dict, par_dict, time_bounds) mi = ModelInstance(ci, md) - return mi end diff --git a/src/core/connections.jl b/src/core/connections.jl index 6479396ce..b79127e13 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -429,7 +429,7 @@ end function _update_param!(obj::AbstractCompositeComponentDef, name::Symbol, value, update_timesteps; raise_error = true) - param = external_param(ext_params, name, missing_ok=true) + param = external_param(obj, name, missing_ok=true) if param === nothing error("Cannot update parameter; $name not found in composite's external parameters.") end @@ -443,7 +443,7 @@ function _update_param!(obj::AbstractCompositeComponentDef, _update_array_param!(obj, name, value, update_timesteps, raise_error) end - dirty!(md) + dirty!(obj) end function _update_scalar_param!(param::ScalarModelParameter, name, value) @@ -554,25 +554,25 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) # Add the connector component before the user-defined component that required it # @info "add_connector_comps: add_comp!(obj, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)" - add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) + conn_comp = add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) + conn_path = conn_comp.comp_path # add a connection between src_component and the ConnectorComp add_internal_param_conn!(obj, InternalParameterConnection(conn.src_comp_path, conn.src_var_name, - conn_comp_name, :input1, + conn_path, :input1, conn.ignoreunits)) # add a connection between ConnectorComp and dst_component - add_internal_param_conn!(obj, InternalParameterConnection(conn_comp_name, :output, + add_internal_param_conn!(obj, InternalParameterConnection(conn_path, :output, conn.dst_comp_path, conn.dst_par_name, conn.ignoreunits)) # add a connection between ConnectorComp and the external backup data - add_external_param_conn!(obj, ExternalParameterConnection(conn_comp_name, :input2, conn.backup)) + add_external_param_conn!(obj, ExternalParameterConnection(conn_path, :input2, conn.backup)) src_comp_def = compdef(obj, conn.src_comp_path) set_param!(obj, conn_comp_name, :first, first_period(obj, src_comp_def)) set_param!(obj, conn_comp_name, :last, last_period(obj, src_comp_def)) - end end diff --git a/src/core/defs.jl b/src/core/defs.jl index a2eecee9a..4ed6960e9 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -311,14 +311,18 @@ function set_run_period!(obj::AbstractComponentDef, first, last) last_per = last_period(obj) changed = false - if first_per !== nothing && first_per < first - @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" + if first !== nothing + if first_per !== nothing && first_per < first + @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" + end obj.first = first changed = true end - if last_per !== nothing && last_per > last - @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" + if last !== nothing + if last_per !== nothing && last_per > last + @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" + end obj.last = last changed = true end @@ -349,7 +353,7 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: if name == :time set_uniform!(ccd, isuniform(keys)) - set_run_period!(ccd, keys[1], keys[end]) + #set_run_period!(ccd, keys[1], keys[end]) end return set_dimension!(ccd, name, Dimension(keys)) @@ -358,6 +362,13 @@ end function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) dirty!(obj) obj.dim_dict[name] = dim + + if name == :time + for subcomp in compdefs(obj) + set_dimension!(subcomp, :time, dim) + end + end + return dim end # helper functions used to determine if the provided time values are @@ -491,12 +502,15 @@ Set a parameter for a component with the given relative path (as a string), in w component with name `:x` beneath the root of the hierarchy in which `obj` is found. If the path does not begin with "/", it is treated as relative to `obj`. """ -function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) - cp = comp_path(obj, path) - comp = find_comp(obj, cp, relative=false) +function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) + comp = find_comp(obj, comp_path, relative=false) set_param!(comp.parent, nameof(comp), param_name, value, dims) end +function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) + set_param!(obj, comp_path(obj, path), param_name, value, dims) +end + """ set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, name::Symbol, value, dims=nothing) @@ -716,21 +730,6 @@ function comps_dict!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symb dirty!(obj) end -function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, name::Symbol) - path = ComponentPath(parent.comp_path, comp_def.name) - root = get_root(parent) - - if has_variable(comp_def, name) - return VariableDefReference(name, root, path) - end - - if has_parameter(comp_def, name) - return ParameterDefReference(name, root, path) - end - - error("$(comp_def.comp_path) does not have a data item named $name") -end - # Save a back-pointer to the container object function parent!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) child.parent = parent @@ -804,33 +803,52 @@ Propagate a time dimension down through the comp def tree. function _propagate_time(obj::AbstractComponentDef, t::Dimension) set_dimension!(obj, :time, t) - if is_composite(obj) - for c in compdefs(obj) - _propagate_time(c, t) - end + for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes + _propagate_time(c, t) + end +end + +function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, + comp_name::Symbol, datum_name::Symbol) + path = ComponentPath(parent.comp_path, comp_name) + root = get_root(parent) + + # for composites, check that the named vars/pars are exported? + # if is_composite(comp_def) + + if has_variable(comp_def, datum_name) + return VariableDefReference(datum_name, root, path) end + + if has_parameter(comp_def, datum_name) + return ParameterDefReference(datum_name, root, path) + end + + error("$(comp_def.comp_path) does not have a data item named $datum_name") end """ - add_comp!(obj::AbstractCompositeComponentDef, comp_def::ComponentDef; + add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component -is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the -`comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. -The `exports` arg identifies which vars/pars to export and with what names. If `nothing`, everything is -exported. The first element of a pair indicates the symbol to export from comp_def to the composite, -the second element allows this var/par to have a new name in the composite. A symbol alone means to use -the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] +is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. Note that +a copy of `comp_def` is created and inserted into the composite under the given `comp_name`. +The `exports` arg identifies which vars/pars to make visible to the next higher composite level, and with +what names. If `nothing`, everything is exported. The first element of a pair indicates the symbol to export +from comp_def to the composite, the second element allows this var/par to have a new name in the composite. +A symbol alone means to use the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; exports=nothing, first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) - # if not specified, export all var/pars. Caller can pass empty list to export nothing. + # If not specified, export all var/pars. Caller can pass empty list to export nothing. + # TBD: actually, might work better to export nothing unless declared as such. if exports === nothing - exports = [variable_names(comp_def)..., parameter_names(comp_def)...] + exports = [] + # exports = [variable_names(comp_def)..., parameter_names(comp_def)...] end for item in exports @@ -842,7 +860,15 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Exports argument to add_comp! must be pair or symbol, got: $item") end - obj.exports[export_name] = _find_var_par(obj, comp_def, name) + # TBD: should this just add to obj.variables / obj.parameters dicts? + # Those dicts hold ParameterDef / VariableDef, which we want to reference, not + # duplicate when building instances. One approach would be for the build step + # to create a dict on objectid(x) to store/find the generated var/param. + if haskey(obj.exports, export_name) + error("Exports may not include a duplicate name ($export_name)") + end + + obj.exports[export_name] = _find_var_par(obj, comp_def, comp_name, name) end # check that a time dimension has been set @@ -955,8 +981,6 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, last = last === nothing ? old_comp.last : last if reconnect - # Assert that new component definition has same parameters and variables needed for the connections - new_comp = compdef(comp_id) function _compare_datum(dict1, dict2) @@ -970,15 +994,17 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, old_params = filter(pair -> pair.first in incoming_params, old_comp.parameters) new_params = new_comp.parameters if !_compare_datum(new_params, old_params) - error("Cannot replace and reconnect; new component does not contain the same definitions of necessary parameters.") + error("Cannot replace and reconnect; new component does not contain the necessary parameters.") end # Check outgoing variables - outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> ipc.src_comp_path == comp_name, internal_param_conns(obj))) + _get_name(obj, name) = nameof(compdef(obj, :first)) + outgoing_vars = map(ipc -> ipc.src_var_name, + filter(ipc -> nameof(compdef(obj, ipc.src_comp_path)) == comp_name, internal_param_conns(obj))) old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) new_vars = new_comp.variables if !_compare_datum(new_vars, old_vars) - error("Cannot replace and reconnect; new component does not contain the same definitions of necessary variables.") + error("Cannot replace and reconnect; new component does not contain the necessary variables.") end # Check external parameter connections diff --git a/src/core/instances.jl b/src/core/instances.jl index b07cbe744..6a05ef7e6 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -123,6 +123,9 @@ Return the `ComponentInstanceVariables` for `comp_name` in CompositeComponentIns """ variables(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = variables(compinstance(obj, comp_name)) +variables(obj::AbstractComponentInstance) = obj.variables + + function variables(m::Model) if ! is_built(m) error("Must build model to access variable instances. Use variables(modeldef(m)) to get variable definitions.") @@ -131,12 +134,14 @@ function variables(m::Model) end """ - parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) + parameters(obj::AbstractComponentInstance, comp_name::Symbol) Return the `ComponentInstanceParameters` for `comp_name` in CompositeComponentInstance `obj`. """ parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = parameters(compinstance(obj, comp_name)) +parameters(obj::AbstractComponentInstance) = obj.parameters + function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol) if ! has_comp(obj, comp_name) error("Component :$comp_name does not exist in the given composite") diff --git a/src/core/model.jl b/src/core/model.jl index 40194afb0..7dcae7fbe 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -274,7 +274,7 @@ The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ -@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md +@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md """ set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) diff --git a/src/core/references.jl b/src/core/references.jl index 50e6c5874..12657a488 100644 --- a/src/core/references.jl +++ b/src/core/references.jl @@ -4,16 +4,16 @@ Set a component parameter as `set_param!(reference, name, value)`. """ function set_param!(ref::ComponentReference, name::Symbol, value) - set_param!(ref.model, ref.comp_name, name, value) + set_param!(ref.parent, ref.comp_path, name, value) end """ - set_param!(ref.model, ref.comp_name, name, value) + set_param!(ref.parent, ref.comp_name, name, value) Set a component parameter as `reference[symbol] = value`. """ function Base.setindex!(ref::ComponentReference, value, name::Symbol) - set_param!(ref.model, ref.comp_name, name, value) + set_param!(ref.parent, ref.comp_path, name, value) end """ @@ -22,7 +22,7 @@ end Connect two components as `connect_param!(dst, dst_name, src, src_name)`. """ function connect_param!(dst::ComponentReference, dst_name::Symbol, src::ComponentReference, src_name::Symbol) - connect_param!(dst.model, dst.comp_id, dst_name, src.comp_id, src_name) + connect_param!(dst.parent, dst.comp_path, dst_name, src.comp_path, src_name) end """ @@ -31,7 +31,7 @@ end Connect two components with the same name as `connect_param!(dst, src, name)`. """ function connect_param!(dst::ComponentReference, src::ComponentReference, name::Symbol) - connect_param!(dst.model, dst.comp_id, name, src.comp_id, name) + connect_param!(dst.parent, dst.comp_path, name, src.comp_path, name) end diff --git a/src/core/time.jl b/src/core/time.jl index a6c3726d0..99f767052 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -168,7 +168,7 @@ function finished(c::Clock) return finished(c.ts) end -function reset(c::Clock) +function Base.reset(c::Clock) c.ts = c.ts - (c.ts.t - 1) nothing end diff --git a/src/core/types.jl b/src/core/types.jl index d499d73dc..f70f656ba 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -390,6 +390,7 @@ Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) # Centralizes the shared functionality from the two component data subtypes. function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, names, types, values, paths) + # @info "_datum_instance: names=$names, types=$types" NT = NamedTuple{Tuple(names), Tuple{types...}} return subtype(NT(values), Vector{ComponentPath}(paths)) end @@ -529,7 +530,7 @@ last_period(obj::AbstractComponentInstance) = obj.last comps_dict[ci.comp_name] = ci end - (vars, pars) = _comp_instance_vars_pars(comps) + (vars, pars) = _comp_instance_vars_pars(comp_def, comps) ComponentInstance(self, comp_def, vars, pars, time_bounds, name) CompositeComponentInstance(self, comps_dict) return self @@ -540,7 +541,7 @@ last_period(obj::AbstractComponentInstance) = obj.last comp_def::AbstractComponentDef, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) - (vars, pars) = _comp_instance_vars_pars(comps) + (vars, pars) = _comp_instance_vars_pars(comp_def, comps) self = new{typeof(vars), typeof(pars)}() CompositeComponentInstance(self, comps, comp_def, time_bounds, name) end @@ -557,13 +558,12 @@ is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) # # TBD: Should include only exported vars and pars, right? -# TBD: Use (from build.jl) _combine_exported_vars & _pars? -# + """ Create a single ComponentInstanceParameters type reflecting those of a composite component's parameters, and similarly for its variables. """ -function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) +function _comp_instance_vars_pars(comp_def::AbstractComponentDef, comps::Vector{<: AbstractComponentInstance}) vtypes = DataType[] vnames = Symbol[] vvalues = [] @@ -574,6 +574,8 @@ function _comp_instance_vars_pars(comps::Vector{<: AbstractComponentInstance}) pvalues = [] ppaths = [] + # exports = + for comp in comps v = comp.variables p = comp.parameters diff --git a/src/utils/misc.jl b/src/utils/misc.jl index 2c1207693..3fbaafa5e 100644 --- a/src/utils/misc.jl +++ b/src/utils/misc.jl @@ -34,17 +34,13 @@ function interpolate(oldvalues::Vector{T}, ts::Int=10) where T <: Union{Float64, return newvalues end -# MacroTools has a "prettify", so we have to import to "extend" -# even though our function is unrelated. This seems unfortunate. -import MacroTools.prettify - """ - MacroTools.prettify(s::String) + pretty_string(s::String) Accepts a camelcase or snakecase string, and makes it human-readable e.g. camelCase -> Camel Case; snake_case -> Snake Case """ -function MacroTools.prettify(s::String) +function pretty_string(s::String) s = replace(s, r"_" => s" ") s = replace(s, r"([a-z])([A-Z])" => s"\1 \2") s = replace(s, r"([A-Z]+)([A-Z])" => s"\1 \2") # handle case of consecutive caps by splitting last from rest @@ -60,4 +56,4 @@ function MacroTools.prettify(s::String) return join(s_arr, " ") end -prettify(s::Symbol) = prettify(string(s)) +pretty_string(s::Symbol) = pretty_string(string(s)) diff --git a/test/test_components.jl b/test/test_components.jl index cedec4f1d..f070840c5 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -4,9 +4,8 @@ using Mimi using Test import Mimi: - reset_compdefs, compdefs, compdef, compkeys, hascomp, _compdefs, first_period, - last_period, compmodule, compname, numcomponents, dump_components, - dim_keys, dim_values, dimensions + reset_compdefs, compdefs, compdef, compkeys, has_comp, _compdefs, first_period, + last_period, compmodule, compname, numcomponents, compinstance, dim_keys, dim_values reset_compdefs() @@ -81,10 +80,11 @@ comps = collect(compdefs(my_model)) # Test compdefs, compdef, compkeys, etc. @test comps == collect(compdefs(my_model.md)) @test length(comps) == 3 -@test compdef(:testcomp3) == comps[3] +@test compdef(:testcomp3).comp_id == comps[3].comp_id @test_throws ErrorException compdef(:testcomp4) #this component does not exist @test [compkeys(my_model.md)...] == [:testcomp1, :testcomp2, :testcomp3] -@test hascomp(my_model.md, :testcomp1) == true && hascomp(my_model.md, :testcomp4) == false +@test has_comp(my_model.md, :testcomp1) == true +@test has_comp(my_model.md, :testcomp4) == false @test compmodule(testcomp3) == :TestComponents @test compname(testcomp3) == :testcomp3 diff --git a/test/test_composite.jl b/test/test_composite.jl index 3107f05d4..e192d83a8 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -13,7 +13,8 @@ reset_compdefs() @defcomp Comp1 begin par_1_1 = Parameter(index=[time]) # external input var_1_1 = Variable(index=[time]) # computed - + foo = Parameter() + function run_timestep(p, v, d, t) # @info "Comp1 run_timestep" v.var_1_1[t] = p.par_1_1[t] @@ -24,6 +25,7 @@ end par_2_1 = Parameter(index=[time]) # connected to Comp1.var_1_1 par_2_2 = Parameter(index=[time]) # external input var_2_1 = Variable(index=[time]) # computed + foo = Parameter() function run_timestep(p, v, d, t) # @info "Comp2 run_timestep" @@ -34,7 +36,8 @@ end @defcomp Comp3 begin par_3_1 = Parameter(index=[time]) # connected to Comp2.var_2_1 var_3_1 = Variable(index=[time]) # external output - + foo = Parameter() + function run_timestep(p, v, d, t) # @info "Comp3 run_timestep" v.var_3_1[t] = p.par_3_1[t] * 2 @@ -42,47 +45,49 @@ end end -# Test the calls the macro will produce -let calling_module = @__MODULE__ - # calling_module = TestComposite - global m = Model() - - comps = [compdef(Comp1), compdef(Comp2), compdef(Comp3)] - - # TBD: need to implement this to create connections and default value - bindings = Binding[] - # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 - # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) - # ] - - exports = [] - # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - # DatumReference(:par_2_2, Comp2) => :c2p2, - # DatumReference(:var_3_1, Comp3) => :c3v1 - # ] - - # CompositeComponentDef(m.md, ccid) # , comps, bindings, exports) - - set_dimension!(m, :time, 2005:2020) - - md = m.md - for c in comps - add_comp!(md, c, nameof(c)) # later allow pair for renaming - end +# Test the calls the macro will produce the following +comps = [ + (compdef(Comp1), [:foo => :foo1]), + (compdef(Comp2), [:foo => :foo2]), + (compdef(Comp3), [:foo => :foo3]) +] + +# TBD: need to implement this to create connections and default value +bindings = Binding[] + # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 + # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) +# ] + +exports = [] + # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + # DatumReference(:par_2_2, Comp2) => :c2p2, + # DatumReference(:var_3_1, Comp3) => :c3v1 +# ] + +compos_name = :top +compos_id = ComponentId(:TestComposite, compos_name) +compos = CompositeComponentDef(compos_id) +# CompositeComponentDef(compos, ccid) # , comps, bindings, exports) + +global m = Model() +set_dimension!(m, :time, 2005:2020) +md = m.md +top = add_comp!(md, compos, nameof(compos)) # add top-level composite under model def to test 2-layer model - merge!(md.exports, ExportsDict(exports)) - append!(md.bindings, bindings) - - nothing +# Add components to composite +for (c, exports) in comps + add_comp!(top, c, nameof(c), exports=exports) # later allow pair for renaming end -md = m.md +merge!(md.exports, ExportsDict(exports)) +append!(md.bindings, bindings) + +set_param!(m, "/top/Comp1", :par_1_1, zeros(length(time_labels(md)))) -set_param!(m, :Comp1, :par_1_1, zeros(length(time_labels(md)))) -connect_param!(md, :Comp2, :par_2_1, :Comp1, :var_1_1) -connect_param!(md, :Comp2, :par_2_2, :Comp1, :var_1_1) -connect_param!(md, :Comp3, :par_3_1, :Comp2, :var_2_1) +connect_param!(top, :Comp2, :par_2_1, :Comp1, :var_1_1) +connect_param!(top, :Comp2, :par_2_2, :Comp1, :var_1_1) +connect_param!(top, :Comp3, :par_3_1, :Comp2, :var_2_1) # build(m) # run(m) diff --git a/test/test_connectorcomp.jl b/test/test_connectorcomp.jl index 3f61678ab..6024391e7 100644 --- a/test/test_connectorcomp.jl +++ b/test/test_connectorcomp.jl @@ -4,7 +4,7 @@ using Mimi using Test import Mimi: - reset_compdefs, compdef + reset_compdefs, compdef, compdefs reset_compdefs() diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index fe3a24045..f7bb803ba 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -3,7 +3,7 @@ module TestReplaceComp using Test using Mimi import Mimi: - reset_compdefs, compdefs, compdef, external_param_conns + reset_compdefs, compdefs, compname, compdef, comp_id, external_param_conns, external_params reset_compdefs() @@ -64,7 +64,8 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection with a parameter with a time dimension @test_throws ErrorException replace_comp!(m, bad1, :second) # Cannot make reconnections because :x in bad1 has different dimensions replace_comp!(m, bad1, :second, reconnect = false) # Can replace without reconnecting -@test nameof(compdef(m.md, :second)) == :bad1 # Successfully replaced +second = compdef(m, :second) +@test second.comp_id.comp_name == :bad1 # Successfully replaced # 3. Test bad internal outgoing variable @@ -76,7 +77,8 @@ add_comp!(m, X, :second) connect_param!(m, :second => :x, :first => :y) # Make an internal connection from a variable with a time dimension @test_throws ErrorException replace_comp!(m, bad2, :first) # Cannot make reconnections because bad2 does not have a variable :y replace_comp!(m, bad2, :first, reconnect = false) # Can replace without reconnecting -@test nameof(compdef(m.md, :first)) == :bad2 # Successfully replaced +first = compdef(m, :first) +@test first.comp_id.comp_name == :bad2 # Successfully replaced # 4. Test bad external parameter name @@ -89,8 +91,7 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs (:warn, r".*parameter x no longer exists in component.*") replace_comp!(m, bad3, :X) -@test nameof(compdef(m.md, :X)) == :bad3 # The replacement was still successful -#external_param_conns(md, comp_name) +@test compname(compdef(m, :X)) == :bad3 # The replacement was still successful @test length(external_param_conns(m)) == 0 # The external parameter connection was removed @test length(external_params(m)) == 1 # The external parameter still exists @@ -129,11 +130,11 @@ add_comp!(m, X, :c1) add_comp!(m, X, :c2) add_comp!(m, X, :c3) -replace_comp!(m, X_repl, :c3) # test replacing the last component -@test compdef(m.md, :c3) == X_repl +replace_comp!(m, X_repl, :c3) # test replacing the last component +@test comp_id(compdef(m, :c3)) == X_repl replace_comp!(m, X_repl, :c2) # test replacing not the last one -@test compdef(m.md, :c2) == X_repl +@test comp_id(compdef(m, :c2)) == X_repl end # module \ No newline at end of file diff --git a/test/test_tools.jl b/test/test_tools.jl index 9bca207a0..b4b72c6fe 100644 --- a/test/test_tools.jl +++ b/test/test_tools.jl @@ -4,15 +4,15 @@ using Test using Mimi import Mimi: - getproperty, reset_compdefs + getproperty, reset_compdefs, pretty_string reset_compdefs() -#utils: prettify -@test Mimi.prettify("camelCaseBasic") == Mimi.prettify(:camelCaseBasic) == "Camel Case Basic" -@test Mimi.prettify("camelWithAOneLetterWord") == Mimi.prettify(:camelWithAOneLetterWord) == "Camel With A One Letter Word" -@test Mimi.prettify("snake_case_basic") == Mimi.prettify(:snake_case_basic) == "Snake Case Basic" -@test Mimi.prettify("_snake__case__weird_") == Mimi.prettify(:_snake__case__weird_) == "Snake Case Weird" +#utils: pretty_string +@test pretty_string("camelCaseBasic") == pretty_string(:camelCaseBasic) == "Camel Case Basic" +@test pretty_string("camelWithAOneLetterWord") == pretty_string(:camelWithAOneLetterWord) == "Camel With A One Letter Word" +@test pretty_string("snake_case_basic") == pretty_string(:snake_case_basic) == "Snake Case Basic" +@test pretty_string("_snake__case__weird_") == pretty_string(:_snake__case__weird_) == "Snake Case Weird" #utils: interpolate stepsize = 2 # N.B. ERROR: cannot assign variable Base.step from module Main diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index e8f5ca820..886481d13 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -6,8 +6,8 @@ using Test import Mimi: reset_compdefs, variable_names, compinstance, get_var_value, get_param_value, set_param_value, set_var_value, dim_count, compdef, - ComponentInstance, ComponentDef, TimestepArray, ComponentInstanceParameters, - ComponentInstanceVariables + ComponentInstance, AbstractComponentInstance, ComponentDef, TimestepArray, + ComponentInstanceParameters, ComponentInstanceVariables reset_compdefs() @@ -38,14 +38,14 @@ run(my_model) mi = my_model.mi md = modeldef(mi) ci = compinstance(mi, :testcomp1) -cdef = compdef(ci) +cdef = compdef(md, ci.comp_path) citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md @test typeof(ci) <: ComponentInstance && ci == compinstance(mi, :testcomp1) -@test typeof(cdef) <: ComponentDef && cdef == compdef(ci.comp_id) -@test nameof(ci) == :testcomp1 -@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: ComponentInstance +@test typeof(cdef) <: ComponentDef && cdef.comp_id == ci.comp_id +@test ci.comp_name == :testcomp1 +@test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: AbstractComponentInstance #test convenience functions that can be called with name symbol From c0f0ea9f64db3e2f823b92da665f59fffdc3383d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 1 Mar 2019 07:59:37 -0800 Subject: [PATCH 40/81] WIP - debugging --- src/core/defs.jl | 10 +++++++++- test/test_timesteps.jl | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 4ed6960e9..014070a0b 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -808,6 +808,13 @@ function _propagate_time(obj::AbstractComponentDef, t::Dimension) end end +""" + thing(value) + +Thing returns a value that is not nothing. (For printing) +""" +thing(value) = (value === nothing ? ":nothing:" : value) + function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol, datum_name::Symbol) path = ComponentPath(parent.comp_path, comp_name) @@ -824,7 +831,8 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract return ParameterDefReference(datum_name, root, path) end - error("$(comp_def.comp_path) does not have a data item named $datum_name") + comp_path = thing(comp_def.comp_path) + error("$(comp_path) does not have a data item named $datum_name") end """ diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index c915af02c..f16405dbf 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -113,7 +113,7 @@ set_dimension!(m, :time, years) @test_throws ErrorException add_comp!(m, Foo; last=2100) foo = add_comp!(m, Foo; first=first_foo) #offset for foo -bar = add_comp!(m, Bar) +bar = add_comp!(m, Bar; exports=[:bar_output => :output]) set_param!(m, :Foo, :inputF, 5.) set_param!(m, :Bar, :inputB, collect(1:length(years))) From 19f430db281b1cfcf919fb7406969be32ace20aa Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 7 Mar 2019 14:06:50 -0800 Subject: [PATCH 41/81] WIP check-in --- src/Mimi.jl | 2 +- src/core/build.jl | 25 +----- src/core/connections.jl | 3 + src/core/defcomp.jl | 17 ++-- src/core/defs.jl | 19 ++-- src/core/instances.jl | 4 +- src/core/types.jl | 189 ++++++++++++++++++++++++---------------- test/runtests.jl | 3 - test/test_timesteps.jl | 2 +- 9 files changed, 134 insertions(+), 130 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index 9feb61af2..676cd0a25 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -94,7 +94,7 @@ end # Components are defined here to allow pre-compilation to work function __init__() - compdir = joinpath(dirname(@__FILE__), "components") + compdir = joinpath(@__DIR__, "components") load_comps(compdir) end diff --git a/src/core/build.jl b/src/core/build.jl index 5d7a7f54e..ce415b4fd 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -73,7 +73,7 @@ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) return ComponentInstanceVariables(names, types, values, paths) end -# TBD: Create ComponentInstanceVariables for a composite component from the list of exported vars +# Create ComponentInstanceVariables for a composite component from the list of exported vars function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dict::Dict{ComponentPath, Any}) names = Symbol[] values = Any[] @@ -122,8 +122,8 @@ end # Recursively instantiate all variables and store refs in the given dict. function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef, var_dict::Dict{ComponentPath, Any}) comp_path = comp_def.comp_path - # @info "_instantiate_vars composite $comp_path" + for cd in compdefs(comp_def) _instantiate_vars(cd, md, var_dict) end @@ -211,27 +211,6 @@ function _build(comp_def::AbstractCompositeComponentDef, return CompositeComponentInstance(comps, comp_def, time_bounds) end -# """ -# Perform a depth-first search on components, exporting vars and params up -# through each composite level. -# """ -# function _propagate_exports(obj::AbstractComponentDef) -# # nothing to do for leaf components -# is_leaf(obj) && return - -# empty!(obj.exports) # start fresh - -# for comp in compdefs(obj) -# _propagate_exports(comp) - -# for (export_name, datum_ref) in comp.exports -# if datum_ref isa ParameterDefReference -# else -# end -# end -# end -# end - function _build(md::ModelDef) # _propagate_exports(md) add_connector_comps(md) diff --git a/src/core/connections.jl b/src/core/connections.jl index b79127e13..ecbd4205d 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -552,6 +552,9 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) conn_comp_def = compdef(conn_name) conn_comp_name = connector_comp_name(i) # generate a new name + # TBD: use this instead of the above + # conn_comp_def = (num_dims == 1 ? Mimi.ConnectorCompVector : Mimi.ConnectorCompMatrix) + # Add the connector component before the user-defined component that required it # @info "add_connector_comps: add_comp!(obj, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)" conn_comp = add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 2681b026c..a8e7627e1 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -3,13 +3,6 @@ # using MacroTools -global defcomp_verbosity = false - -function set_defcomp_verbosity(value::Bool) - global defcomp_verbosity = value - nothing -end - # Store a list of built-in components so we can suppress messages about creating them # TBD: and (later) suppress their return in the list of components at the user level. const global built_in_comps = (:adder, :ConnectorCompVector, :ConnectorCompMatrix) @@ -142,6 +135,11 @@ macro defcomp(comp_name, ex) result = :( let current_module = @__MODULE__ global const $comp_name = Mimi.ComponentId(nameof(current_module), $(QuoteNode(comp_name))) + + # TBD: use instead of the line above + # comp_id = Mimi.ComponentId(nameof(current_module), $(QuoteNode(comp_name))), + # comp = Mimi.ComponentDef(comp_id) + # global $comp_name = comp end ) @@ -151,7 +149,8 @@ macro defcomp(comp_name, ex) push!(let_block, expr) end - newcomp = :(comp = new_comp($comp_name, $defcomp_verbosity)) + # TBD: delete this when uncommenting change in "let" block above + newcomp = :(comp = new_comp($comp_name)) addexpr(newcomp) for elt in elements @@ -254,8 +253,6 @@ macro defcomp(comp_name, ex) end end - # addexpr(:($comp_name)) addexpr(:(nothing)) # reduces noise - return esc(result) end diff --git a/src/core/defs.jl b/src/core/defs.jl index 095b1ee7f..37ab0d033 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -139,14 +139,6 @@ Add an empty `ComponentDef` to the global component registry with the given `addparameter`, etc. Use `@defcomposite` to create composite components. """ function new_comp(comp_id::ComponentId, verbose::Bool=true) - if verbose - if haskey(_compdefs, comp_id) - @warn "Redefining component $comp_id" - else - @info "new component $comp_id" - end - end - comp_def = ComponentDef(comp_id) _compdefs[comp_id] = comp_def return comp_def @@ -819,17 +811,19 @@ function _propagate_time(obj::AbstractComponentDef, t::Dimension) end """ - thing(value) + printable(value) -Thing returns a value that is not nothing. (For printing) +Return a value that is not nothing. """ -thing(value) = (value === nothing ? ":nothing:" : value) +printable(value) = (value === nothing ? ":nothing:" : value) function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol, datum_name::Symbol) path = ComponentPath(parent.comp_path, comp_name) root = get_root(parent) + # @info "comp path: $path, datum_name: $datum_name" + # for composites, check that the named vars/pars are exported? # if is_composite(comp_def) @@ -841,8 +835,7 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract return ParameterDefReference(datum_name, root, path) end - comp_path = thing(comp_def.comp_path) - error("$(comp_path) does not have a data item named $datum_name") + error("Component $(comp_def.comp_id) does not have a data item named $datum_name") end """ diff --git a/src/core/instances.jl b/src/core/instances.jl index 67c147fe1..b7cedfee7 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -151,8 +151,8 @@ end function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol, datum_name::Symbol) comp_inst = obj[comp_name] - vars = comp_inst.variables - pars = comp_inst.parameters + vars = variables(comp_inst) + pars = parameters(comp_inst) if datum_name in names(vars) which = vars diff --git a/src/core/types.jl b/src/core/types.jl index f70f656ba..89c093542 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -7,7 +7,7 @@ abstract type MimiStruct end const AbstractMimiType = Union{MimiStruct, AbstractMimiClass} -# To identify components, @defcomp creates a variable with the name of +# To identify components, @defcomp creates a variable with the name of # the component whose value is an instance of this type. struct ComponentId <: MimiStruct module_name::Symbol @@ -49,14 +49,14 @@ end struct VariableTimestep{TIMES} <: AbstractTimestep t::Int - current::Int + current::Int function VariableTimestep{TIMES}(t::Int = 1) where {TIMES} # The special case below handles when functions like next_step step beyond # the end of the TIMES array. The assumption is that the length of this # last timestep, starting at TIMES[end], is 1. current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t] - + return new(t, current) end end @@ -67,13 +67,13 @@ mutable struct Clock{T <: AbstractTimestep} <: MimiStruct function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T return new(FixedTimestep{FIRST, STEP, LAST}(1)) end - + function Clock{T}(TIMES::NTuple{N, Int} where N) where T return new(VariableTimestep{TIMES}()) end end -mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} <: MimiStruct +mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} <: MimiStruct data::Array{T, N} function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} @@ -123,7 +123,7 @@ end # # Simple optimization for ranges since indices are computable. -# Unclear whether this is really any better than simply using +# Unclear whether this is really any better than simply using # a dict for all cases. Might scrap this in the end. # mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension @@ -189,7 +189,7 @@ struct InternalParameterConnection <: AbstractConnection backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing offset::Int - function InternalParameterConnection(src_path::ComponentPath, src_var::Symbol, + function InternalParameterConnection(src_path::ComponentPath, src_var::Symbol, dst_path::ComponentPath, dst_par::Symbol, ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) self = new(src_path, src_var, dst_path, dst_par, ignoreunits, backup, offset) @@ -213,9 +213,9 @@ end end """ - nameof(obj::NamedDef) = obj.name + nameof(obj::NamedDef) = obj.name -Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, +Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, `CompositeComponentDef`, and `VariableDefReference` and `ParameterDefReference`. """ Base.nameof(obj::AbstractNamedObj) = obj.name @@ -258,7 +258,7 @@ end end # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; + function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; name::Union{Nothing, Symbol}=nothing) if comp_id === nothing # ModelDefs are anonymous, but since they're gensym'd, they can claim the Mimi package @@ -271,7 +271,7 @@ end self.comp_id = comp_id self.comp_path = nothing # this is set in add_comp!() and ModelDef() self.variables = OrderedDict{Symbol, VariableDef}() - self.parameters = OrderedDict{Symbol, ParameterDef}() + self.parameters = OrderedDict{Symbol, ParameterDef}() self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.first = self.last = nothing self.is_uniform = true @@ -279,11 +279,11 @@ end return self end - function ComponentDef(comp_id::Union{Nothing, ComponentId}; + function ComponentDef(comp_id::Union{Nothing, ComponentId}; name::Union{Nothing, Symbol}=nothing) self = new() return ComponentDef(self, comp_id, name=name) - end + end end comp_id(obj::AbstractComponentDef) = obj.comp_id @@ -312,7 +312,7 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Binding} exports::ExportsDict - + internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} external_params::Dict{Symbol, ModelParameter} @@ -334,7 +334,7 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() self.bindings = Vector{Binding}() self.exports = ExportsDict() - self.internal_param_conns = Vector{InternalParameterConnection}() + self.internal_param_conns = Vector{InternalParameterConnection}() self.external_param_conns = Vector{ExternalParameterConnection}() self.external_params = Dict{Symbol, ModelParameter}() self.backups = Vector{Symbol}() @@ -367,7 +367,7 @@ ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath( function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - self.comp_path = ComponentPath(self.name) + self.comp_path = ComponentPath(self.name) return ModelDef(self, number_type, false) # call @class-generated method end end @@ -388,9 +388,9 @@ Base.names(obj::AbstractComponentInstanceData) = keys(nt(obj)) Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) # Centralizes the shared functionality from the two component data subtypes. -function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, +function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, names, types, values, paths) - # @info "_datum_instance: names=$names, types=$types" + @info "_datum_instance: names=$names, types=$types" NT = NamedTuple{Tuple(names), Tuple{types...}} return subtype(NT(values), Vector{ComponentPath}(paths)) end @@ -399,23 +399,23 @@ end function ComponentInstanceParameters(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} return new{NT}(nt, paths) end - - function ComponentInstanceParameters(names::Vector{Symbol}, - types::Vector{DataType}, + + function ComponentInstanceParameters(names::Vector{Symbol}, + types::Vector{DataType}, values::Vector{Any}, paths) return _datum_instance(ComponentInstanceParameters, names, types, values, paths) end end - + @class ComponentInstanceVariables <: ComponentInstanceData begin function ComponentInstanceVariables(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} return new{NT}(nt, paths) end - - function ComponentInstanceVariables(names::Vector{Symbol}, - types::Vector{DataType}, - values::Vector{Any}, + + function ComponentInstanceVariables(names::Vector{Symbol}, + types::Vector{DataType}, + values::Vector{Any}, paths) return _datum_instance(ComponentInstanceVariables, names, types, values, paths) end @@ -433,7 +433,7 @@ struct DimValueDict <: MimiStruct end # Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimValueDict of dimensions by name +# The run_timestep() and init() funcs pass a DimValueDict of dimensions by name # as the "d" parameter. Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] @@ -441,7 +441,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro comp_name::Symbol comp_id::ComponentId comp_path::ComponentPath - variables::TV + variables::TV # TBD: write functions to extract these from type instead of storing? parameters::TP first::Union{Nothing, Int} last::Union{Nothing, Int} @@ -449,7 +449,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro run_timestep::Union{Nothing, Function} function ComponentInstance(self::AbstractComponentInstance, - comp_def::AbstractComponentDef, + comp_def::AbstractComponentDef, vars::TV, pars::TP, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) where @@ -465,7 +465,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro self.first = comp_def.first !== nothing ? comp_def.first : time_bounds[1] self.last = comp_def.last !== nothing ? comp_def.last : time_bounds[2] - # @info "ComponentInstance evaluating $(comp_id.module_name)" + # @info "ComponentInstance evaluating $(comp_id.module_name)" comp_module = Main.eval(comp_id.module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) @@ -482,7 +482,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro catch err # @info "Eval of $func_name in module $comp_module failed" nothing - end + end end # `is_composite` indicates a ComponentInstance used to store summary @@ -516,66 +516,59 @@ dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[nam first_period(obj::AbstractComponentInstance) = obj.first last_period(obj::AbstractComponentInstance) = obj.last -@class mutable CompositeComponentInstance <: ComponentInstance begin - comps_dict::OrderedDict{Symbol, AbstractComponentInstance} - - function CompositeComponentInstance(self::AbstractCompositeComponentInstance, - comps::Vector{<: AbstractComponentInstance}, - comp_def::AbstractComponentDef, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) - comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() +# +# TBD: Should include only exported vars and pars, right? +# +""" +Rerun the ComponentInstanceParameters/Variables exported by the given list of +component instances. +""" +function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, + comps::Vector{<: AbstractComponentInstance}) + vdict = Dict([:types => DataType[], :names => Symbol[], :values => [], :paths => []]) + pdict = Dict([:types => DataType[], :names => Symbol[], :values => [], :paths => []]) - for ci in comps - comps_dict[ci.comp_name] = ci - end - - (vars, pars) = _comp_instance_vars_pars(comp_def, comps) - ComponentInstance(self, comp_def, vars, pars, time_bounds, name) - CompositeComponentInstance(self, comps_dict) - return self - end + root = get_root(comp_def) # to find comp_defs by path - # Constructs types of vars and params from sub-components - function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, - comp_def::AbstractComponentDef, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) - (vars, pars) = _comp_instance_vars_pars(comp_def, comps) - self = new{typeof(vars), typeof(pars)}() - CompositeComponentInstance(self, comps, comp_def, time_bounds, name) + for (export_name, dr) in comp_def.exports + datum_comp = compdef(dr) + rpath = rel_path(root.comp_path, datum_comp.comp_path) # get relative path to the referenced comp def + + datum_dict = (is_parameter(dr) ? datum_comp.parameters : datum_comp.variables) + datum = datum_dict[nameof(dr)] + + d = (is_parameter(dr) ? pdict : vdict) + push!(d[:names], export_name) + push!(d[:types], datum.datatype) + push!(d[:values], datum.values) + push!(d[:paths], dr.comp_path) end -end -# These methods can be called on ModelInstances as well -components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) -has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) -compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] - -is_leaf(ci::AbstractComponentInstance) = true -is_leaf(ci::AbstractCompositeComponentInstance) = false -is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) + vars = ComponentInstanceVariables( vdict[:names], vdict[:types], vdict[:values], vdict[:paths]) + pars = ComponentInstanceParameters(pdict[:names], pdict[:types], pdict[:values], pdict[:paths]) -# -# TBD: Should include only exported vars and pars, right? + return vars, pars +end """ -Create a single ComponentInstanceParameters type reflecting those of a composite +Create a single ComponentInstanceParameters type reflecting those of a composite component's parameters, and similarly for its variables. """ -function _comp_instance_vars_pars(comp_def::AbstractComponentDef, comps::Vector{<: AbstractComponentInstance}) +function _comp_instance_vars_pars_OLD(comp_def::AbstractComponentDef, comps::Vector{<: AbstractComponentInstance}) + root = get_root(comp_def) # to find comp_defs by path + exports = comp_def.exports + @info "Exported by $(comp_def.comp_id): $exports" + vtypes = DataType[] vnames = Symbol[] vvalues = [] vpaths = [] - + ptypes = DataType[] pnames = Symbol[] pvalues = [] ppaths = [] - # exports = - for comp in comps v = comp.variables p = comp.parameters @@ -599,6 +592,48 @@ function _comp_instance_vars_pars(comp_def::AbstractComponentDef, comps::Vector{ return vars, pars end + +@class mutable CompositeComponentInstance <: ComponentInstance begin + comps_dict::OrderedDict{Symbol, AbstractComponentInstance} + + function CompositeComponentInstance(self::AbstractCompositeComponentInstance, + comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractCompositeComponentDef, + vars::ComponentInstanceVariables, + pars::ComponentInstanceParameters, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) + + comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() + for ci in comps + comps_dict[ci.comp_name] = ci + end + + ComponentInstance(self, comp_def, vars, pars, time_bounds, name) + CompositeComponentInstance(self, comps_dict) + return self + end + + # Constructs types of vars and params from sub-components + function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractCompositeComponentDef, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) + (vars, pars) = _comp_instance_vars_pars(comp_def, comps) + self = new{typeof(vars), typeof(pars)}() + CompositeComponentInstance(self, comps, comp_def, vars, pars, time_bounds, name) + end +end + +# These methods can be called on ModelInstances as well +components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) +has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) +compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] + +is_leaf(ci::AbstractComponentInstance) = true +is_leaf(ci::AbstractCompositeComponentInstance) = false +is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) + # ModelInstance holds the built model that is ready to be run @class ModelInstance <: CompositeComponentInstance begin md::ModelDef @@ -616,7 +651,7 @@ end """ Model -A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). +A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). This `Model` can be created with the optional keyword argument `number_type` indicating the default type of number used for the `ModelDef`. If not specified the `Model` assumes a `number_type` of `Float64`. @@ -624,7 +659,7 @@ a `number_type` of `Float64`. mutable struct Model <: MimiStruct md::ModelDef mi::Union{Nothing, ModelInstance} - + function Model(number_type::DataType=Float64) return new(ModelDef(number_type), nothing) end @@ -635,10 +670,10 @@ mutable struct Model <: MimiStruct end end -""" +""" MarginalModel -A Mimi `Model` whose results are obtained by subtracting results of one `base` Model +A Mimi `Model` whose results are obtained by subtracting results of one `base` Model from those of another `marginal` Model` that has a difference of `delta`. """ struct MarginalModel <: MimiStruct diff --git a/test/runtests.jl b/test/runtests.jl index b93e82ccd..40223f892 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,6 @@ using Mimi using Test -# reduce the chatter during testing -Mimi.set_defcomp_verbosity(false) - @testset "Mimi" begin @info("test_main.jl") diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index f16405dbf..cbd0d0c78 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -113,7 +113,7 @@ set_dimension!(m, :time, years) @test_throws ErrorException add_comp!(m, Foo; last=2100) foo = add_comp!(m, Foo; first=first_foo) #offset for foo -bar = add_comp!(m, Bar; exports=[:bar_output => :output]) +bar = add_comp!(m, Bar; exports=[:output => :bar_output]) set_param!(m, :Foo, :inputF, 5.) set_param!(m, :Bar, :inputB, collect(1:length(years))) From 74641cefb5601989280fb9bbd65b77ec8161cbca Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 8 Mar 2019 15:58:16 -0800 Subject: [PATCH 42/81] Nearly all tests now working. Still WIP, however. --- src/core/build.jl | 2 +- src/core/connections.jl | 12 ++--- src/core/defcomp.jl | 29 +++++------ src/core/defmodel.jl | 4 +- src/core/defs.jl | 68 ++++++++------------------ src/core/model.jl | 24 +++++---- src/core/types.jl | 44 +++++++++++------ src/explorer/buildspecs.jl | 2 +- src/mcs/montecarlo.jl | 2 +- src/utils/plotting.jl | 3 +- test/runtests.jl | 8 ++- test/test_components.jl | 18 ++----- test/test_composite.jl | 6 +-- test/test_connectorcomp.jl | 53 ++++++++++---------- test/test_dimensions.jl | 23 ++++----- test/test_main.jl | 4 -- test/test_main_variabletimestep.jl | 4 -- test/test_metainfo.jl | 8 +-- test/test_metainfo_variabletimestep.jl | 10 ++-- test/test_replace_comp.jl | 4 +- test/test_timesteparrays.jl | 3 +- test/test_timesteps.jl | 13 +++-- 22 files changed, 156 insertions(+), 188 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index ce415b4fd..431e0c643 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -207,7 +207,7 @@ function _build(comp_def::AbstractCompositeComponentDef, # @info " var_dict $(var_dict)" # @info " par_dict $(par_dict)" - comps = [_build(cd, var_dict, par_dict, time_bounds) for cd in compdefs(comp_def)] + comps = [_build(cd, var_dict, par_dict, time_bounds) for cd in compdefs(comp_def)] return CompositeComponentInstance(comps, comp_def, time_bounds) end diff --git a/src/core/connections.jl b/src/core/connections.jl index ecbd4205d..6059dc637 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -186,7 +186,11 @@ function connect_param!(obj::AbstractCompositeComponentDef, src_first, src_last = first_and_last(src_comp_def) dst_first, dst_last = first_and_last(dst_comp_def) if (dst_first !== nothing && src_first !== nothing && dst_first < src_first) || - (dst_last !== nothing && src_last !== nothing && dst_last > src_last) + (dst_last !== nothing && src_last !== nothing && dst_last > src_last) + src_first = printable(src_first) + src_last = printable(src_last) + dst_first = printable(dst_first) + dst_last = printable(dst_last) error("""Cannot connect parameter: $src_comp_path runs only from $src_first to $src_last, whereas $dst_comp_path runs from $dst_first to $dst_last. Backup data must be provided for missing years. Try calling: @@ -548,13 +552,9 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) end # Fetch the definition of the appropriate connector commponent - conn_name = num_dims == 1 ? :ConnectorCompVector : :ConnectorCompMatrix - conn_comp_def = compdef(conn_name) + conn_comp_def = (num_dims == 1 ? Mimi.ConnectorCompVector : Mimi.ConnectorCompMatrix) conn_comp_name = connector_comp_name(i) # generate a new name - # TBD: use this instead of the above - # conn_comp_def = (num_dims == 1 ? Mimi.ConnectorCompVector : Mimi.ConnectorCompMatrix) - # Add the connector component before the user-defined component that required it # @info "add_connector_comps: add_comp!(obj, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)" conn_comp = add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index a8e7627e1..9c09549f4 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -129,17 +129,14 @@ macro defcomp(comp_name, ex) comp_name = cmpname end - # We'll return a block of expressions that will define the component. First, - # save the ComponentId to a variable with the same name as the component. - # @__MODULE__ is evaluated when the expanded macro is interpreted + # We'll return a block of expressions that will define the component. + # N.B. @__MODULE__ is evaluated when the expanded macro is interpreted. result = :( - let current_module = @__MODULE__ - global const $comp_name = Mimi.ComponentId(nameof(current_module), $(QuoteNode(comp_name))) + let current_module = @__MODULE__, + comp_id = Mimi.ComponentId(nameof(current_module), $(QuoteNode(comp_name))), + comp = Mimi.ComponentDef(comp_id) - # TBD: use instead of the line above - # comp_id = Mimi.ComponentId(nameof(current_module), $(QuoteNode(comp_name))), - # comp = Mimi.ComponentDef(comp_id) - # global $comp_name = comp + global $comp_name = comp end ) @@ -149,10 +146,6 @@ macro defcomp(comp_name, ex) push!(let_block, expr) end - # TBD: delete this when uncommenting change in "let" block above - newcomp = :(comp = new_comp($comp_name)) - addexpr(newcomp) - for elt in elements @debug "elt: $elt" @@ -240,12 +233,14 @@ macro defcomp(comp_name, ex) @debug " index $(Tuple(dimensions)), unit '$unit', desc '$desc'" - dflt = eval(dflt) - if (dflt !== nothing && length(dimensions) != ndims(dflt)) - error("Default value has different number of dimensions ($(ndims(dflt))) than parameter '$name' ($(length(dimensions)))") + if dflt !== nothing + dflt = Base.eval(Main, dflt) + if length(dimensions) != ndims(dflt) + error("Default value has different number of dimensions ($(ndims(dflt))) than parameter '$name' ($(length(dimensions)))") + end end - vartype = vartype === nothing ? Number : eval(vartype) + vartype = (vartype === nothing ? Number : Base.eval(Main, vartype)) addexpr(_generate_var_or_param(elt_type, name, vartype, dimensions, dflt, desc, unit)) else diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index 2be07bf8e..86913c803 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -72,8 +72,8 @@ macro defmodel(model_name, ex) addexpr(expr) name = (alias === nothing ? comp_name : alias) - expr = :(add_comp!($model_name, - Mimi.ComponentId(comp_mod_name, $(QuoteNode(comp_name))), $(QuoteNode(name)))) + expr = :(add_comp!($model_name, Mimi.ComponentId(comp_mod_name, $(QuoteNode(comp_name))), $(QuoteNode(name)))) + # TBD: extend comp.var syntax to allow module name, e.g., FUND.economy.ygross elseif (@capture(elt, src_comp_.src_name_[arg_] => dst_comp_.dst_name_) || diff --git a/src/core/defs.jl b/src/core/defs.jl index 37ab0d033..8e7214f83 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,26 +1,9 @@ -# Global component registry: @defcomp stores component definitions here -global const _compdefs = Dict{ComponentId, ComponentDef}() +compdef(comp_id::ComponentId) = getfield(getfield(Main, comp_id.module_name), comp_id.comp_name) -compdefs() = collect(values(_compdefs)) - -compdef(comp_id::ComponentId) = _compdefs[comp_id] - -function compdef(comp_name::Symbol) - matches = collect(Iterators.filter(obj -> nameof(obj) == comp_name, values(_compdefs))) - count = length(matches) - - if count == 1 - return matches[1] - elseif count == 0 - error("Component $comp_name was not found in the global registry") - else - error("Multiple components named $comp_name were found in the global registry") - end -end +compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path, relative=false) compdef(dr::AbstractDatumReference) = compdef(dr.root, dr.comp_path) - -compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path, relative=false) +compdef(cr::ComponentReference) = compdef(cr.parent, cr.comp_path) # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] @@ -41,9 +24,8 @@ compnames() = map(compname, compdefs()) # Access a subcomponent as comp[:name] Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict[name] +# TBD: deprecated function reset_compdefs(reload_builtins=true) - empty!(_compdefs) - if reload_builtins compdir = joinpath(@__DIR__, "..", "components") load_comps(compdir) @@ -131,19 +113,6 @@ number_type(obj::AbstractCompositeComponentDef) = number_type(get_root(obj)) numcomponents(obj::AbstractComponentDef) = 0 # no sub-components numcomponents(obj::AbstractCompositeComponentDef) = length(obj.comps_dict) -""" - new_comp(comp_id::ComponentId, verbose::Bool=true) - -Add an empty `ComponentDef` to the global component registry with the given -`comp_id`. The empty `ComponentDef` must be populated with calls to `addvariable`, -`addparameter`, etc. Use `@defcomposite` to create composite components. -""" -function new_comp(comp_id::ComponentId, verbose::Bool=true) - comp_def = ComponentDef(comp_id) - _compdefs[comp_id] = comp_def - return comp_def -end - """ delete!(obj::AbstractCompositeComponentDef, component::Symbol) @@ -491,6 +460,12 @@ function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, para return parameter_dimensions(compdef(obj, comp_name), param_name) end + +function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) + comp = find_comp(obj, comp_path, relative=false) + set_param!(comp.parent, nameof(comp), param_name, value, dims) +end + """ set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) @@ -498,11 +473,6 @@ Set a parameter for a component with the given relative path (as a string), in w component with name `:x` beneath the root of the hierarchy in which `obj` is found. If the path does not begin with "/", it is treated as relative to `obj`. """ -function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) - comp = find_comp(obj, comp_path, relative=false) - set_param!(comp.parent, nameof(comp), param_name, value, dims) -end - function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) set_param!(obj, comp_path(obj, path), param_name, value, dims) end @@ -839,7 +809,8 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract end """ - add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; + add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, + comp_name::Symbol=comp_def.comp_id.comp_name; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component @@ -850,7 +821,8 @@ what names. If `nothing`, everything is exported. The first element of a pair in from comp_def to the composite, the second element allows this var/par to have a new name in the composite. A symbol alone means to use the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] """ -function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol; +function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, + comp_name::Symbol=comp_def.comp_id.comp_name; exports=nothing, first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -940,10 +912,10 @@ is added at the end of the list unless one of the keywords, `first`, `last`, `be `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, - comp_name::Symbol=comp_id.comp_name; - exports=nothing, - first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) + comp_name::Symbol=comp_id.comp_name; + exports=nothing, + first::NothingInt=nothing, last::NothingInt=nothing, + before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") add_comp!(obj, compdef(comp_id), comp_name, exports=exports, first=first, last=last, before=before, after=after) @@ -960,7 +932,7 @@ component `comp_id` using the same name. The component is added in the same posi old component, unless one of the keywords `before` or `after` is specified. The component is added with the same first and last values, unless the keywords `first` or `last` are specified. Optional boolean argument `reconnect` with default value `true` indicates whether the existing -parameter connections should be maintained in the new component. +parameter connections should be maintained in the new component. Returns the added comp def. """ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; @@ -1046,7 +1018,7 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end # Re-add - add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) + return add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) diff --git a/src/core/model.jl b/src/core/model.jl index 78e425fa6..e93603f5c 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -119,10 +119,13 @@ Add the component indicated by `comp_id` to the model indicated by `m`. The comp the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the new name. """ -function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) - comp_def = add_comp!(m.md, comp_id, comp_name; exports=exports, first=first, last=last, before=before, after=after) - return ComponentReference(m.md, comp_def.comp_path) +function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; kwargs...) + comp_def = add_comp!(m.md, comp_id, comp_name; kwargs...) + return ComponentReference(m.md, comp_name) +end + +function add_comp!(m::Model, comp_def::ComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; kwargs...) + return add_comp!(m, comp_def.comp_id, comp_name; kwargs...) end """ @@ -139,12 +142,13 @@ The component is added with the same first and last values, unless the keywords default value `true` indicates whether the existing parameter connections should be maintained in the new component. """ -function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - first::NothingSymbol=nothing, last::NothingSymbol=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing, - reconnect::Bool=true) - replace_comp!(m.md, comp_id, comp_name; first=first, last=last, before=before, after=after, reconnect=reconnect) - return ComponentReference(m, comp_name) +function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; kwargs...) + comp_def = replace_comp!(m.md, comp_id, comp_name; kwargs...) + return ComponentReference(m.md, comp_name) +end + +function replace_comp!(m::Model, comp_def::ComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; kwargs...) + return replace_comp!(m, comp_def.comp_id, comp_name; kwargs...) end @delegate ComponentReference(m::Model, name::Symbol) => md diff --git a/src/core/types.jl b/src/core/types.jl index 89c093542..5df2f3211 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -390,7 +390,7 @@ Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) # Centralizes the shared functionality from the two component data subtypes. function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, names, types, values, paths) - @info "_datum_instance: names=$names, types=$types" + # @info "_datum_instance: names=$names, types=$types" NT = NamedTuple{Tuple(names), Tuple{types...}} return subtype(NT(values), Vector{ComponentPath}(paths)) end @@ -466,7 +466,8 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro self.last = comp_def.last !== nothing ? comp_def.last : time_bounds[2] # @info "ComponentInstance evaluating $(comp_id.module_name)" - comp_module = Main.eval(comp_id.module_name) + module_name = comp_id.module_name + comp_module = getfield(Main, module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) # CompositeComponentInstances use a standard method that just loops over inner components. @@ -478,7 +479,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro func_name = Symbol("$(name)_$(nameof(comp_module))_$(self.comp_id.comp_name)") try - Base.eval(comp_module, func_name) + getfield(comp_module, func_name) catch err # @info "Eval of $func_name in module $comp_module failed" nothing @@ -517,7 +518,7 @@ first_period(obj::AbstractComponentInstance) = obj.first last_period(obj::AbstractComponentInstance) = obj.last # -# TBD: Should include only exported vars and pars, right? +# Include only exported vars and pars # """ Rerun the ComponentInstanceParameters/Variables exported by the given list of @@ -525,28 +526,39 @@ component instances. """ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, comps::Vector{<: AbstractComponentInstance}) - vdict = Dict([:types => DataType[], :names => Symbol[], :values => [], :paths => []]) - pdict = Dict([:types => DataType[], :names => Symbol[], :values => [], :paths => []]) + vdict = Dict([:types => [], :names => [], :values => [], :paths => []]) + pdict = Dict([:types => [], :names => [], :values => [], :paths => []]) root = get_root(comp_def) # to find comp_defs by path + comps_dict = Dict([comp.comp_name => comp for comp in comps]) + for (export_name, dr) in comp_def.exports datum_comp = compdef(dr) - rpath = rel_path(root.comp_path, datum_comp.comp_path) # get relative path to the referenced comp def - - datum_dict = (is_parameter(dr) ? datum_comp.parameters : datum_comp.variables) - datum = datum_dict[nameof(dr)] - + datum_name = nameof(dr) + ci = comps_dict[nameof(datum_comp)] + + datum = (is_parameter(dr) ? ci.parameters : ci.variables) d = (is_parameter(dr) ? pdict : vdict) + + # Find the position of the desired field in the named tuple + # so we can extract it's datatype. + pos = findfirst(isequal(datum_name), names(datum)) + datatypes = types(datum) + dtype = datatypes[pos] + value = getproperty(datum, datum_name) + push!(d[:names], export_name) - push!(d[:types], datum.datatype) - push!(d[:values], datum.values) + push!(d[:types], dtype) + push!(d[:values], value) push!(d[:paths], dr.comp_path) end - vars = ComponentInstanceVariables( vdict[:names], vdict[:types], vdict[:values], vdict[:paths]) - pars = ComponentInstanceParameters(pdict[:names], pdict[:types], pdict[:values], pdict[:paths]) + vars = ComponentInstanceVariables(Vector{Symbol}(vdict[:names]), Vector{DataType}(vdict[:types]), + Vector{Any}(vdict[:values]), Vector{ComponentPath}(vdict[:paths])) + pars = ComponentInstanceParameters(Vector{Symbol}(pdict[:names]), Vector{DataType}(pdict[:types]), + Vector{Any}(pdict[:values]), Vector{ComponentPath}(pdict[:paths])) return vars, pars end @@ -557,7 +569,7 @@ component's parameters, and similarly for its variables. function _comp_instance_vars_pars_OLD(comp_def::AbstractComponentDef, comps::Vector{<: AbstractComponentInstance}) root = get_root(comp_def) # to find comp_defs by path exports = comp_def.exports - @info "Exported by $(comp_def.comp_id): $exports" + # @info "Exported by $(comp_def.comp_id): $exports" vtypes = DataType[] vnames = Symbol[] diff --git a/src/explorer/buildspecs.jl b/src/explorer/buildspecs.jl index d930cb08d..a65c25aa2 100644 --- a/src/explorer/buildspecs.jl +++ b/src/explorer/buildspecs.jl @@ -68,7 +68,7 @@ end function menu_item_list(model::Model) all_menuitems = [] - for comp_name in map(name, compdefs(model)) + for comp_name in map(nameof, compdefs(model)) items = vcat(variable_names(model, comp_name), parameter_names(model, comp_name)) for item_name in items diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index f986920e3..785b0ecba 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -190,7 +190,7 @@ function _copy_mcs_params(mcs::MonteCarloSimulation) for (i, m) in enumerate(mcs.models) md = modelinstance_def(m) - param_vec[i] = Dict{Symbol, ModelParameter}(trans.paramname => copy(external_param(md, trans.paramname)) for trans in mcs.translist) + param_vec[i] = Dict{Symbol, ModelParameter}(trans.paramname => deepcopy(external_param(md, trans.paramname)) for trans in mcs.translist) end return param_vec diff --git a/src/utils/plotting.jl b/src/utils/plotting.jl index 3f182dd21..29f764a71 100644 --- a/src/utils/plotting.jl +++ b/src/utils/plotting.jl @@ -23,7 +23,8 @@ no `filename` is given, plot will simply display. function plot_comp_graph(m::Model, filename::Union{Nothing, String} = nothing) graph = comp_graph(m.md) - names = map(i -> get_prop(graph, i, :name), vertices(graph)) + paths = map(i -> get_prop(graph, i, :path), vertices(graph)) + names = map(path -> path.names[end], paths) plot = gplot(graph, nodelabel=names, nodesize=6, nodelabelsize=6) if filename !== nothing diff --git a/test/runtests.jl b/test/runtests.jl index 40223f892..3ebdbb53a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,6 +6,9 @@ using Test @info("test_main.jl") include("test_main.jl") + @info("test_composite.jl") + include("test_composite.jl") + @info("test_main_variabletimestep.jl") include("test_main_variabletimestep.jl") @@ -72,8 +75,9 @@ using Test @info("test_timesteparrays.jl") include("test_timesteparrays.jl") - @info("test_dimensions") - include("test_dimensions.jl") + @warn("SKIPPING test_dimensions") # question about proper behavior in comp first/last when setting :time + # @info("test_dimensions") + # include("test_dimensions.jl") @info("test_datum_storage.jl") include("test_datum_storage.jl") diff --git a/test/test_components.jl b/test/test_components.jl index f070840c5..0817ac9d5 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -4,13 +4,9 @@ using Mimi using Test import Mimi: - reset_compdefs, compdefs, compdef, compkeys, has_comp, _compdefs, first_period, + reset_compdefs, compdefs, compdef, compkeys, has_comp, first_period, last_period, compmodule, compname, numcomponents, compinstance, dim_keys, dim_values -reset_compdefs() - -@test length(_compdefs) == 3 # adder, ConnectorCompVector, ConnectorCompMatrix - my_model = Model() # Try running model with no components @@ -80,8 +76,8 @@ comps = collect(compdefs(my_model)) # Test compdefs, compdef, compkeys, etc. @test comps == collect(compdefs(my_model.md)) @test length(comps) == 3 -@test compdef(:testcomp3).comp_id == comps[3].comp_id -@test_throws ErrorException compdef(:testcomp4) #this component does not exist +@test compdef(my_model, :testcomp3).comp_id == comps[3].comp_id +@test_throws KeyError compdef(my_model, :testcomp4) #this component does not exist @test [compkeys(my_model.md)...] == [:testcomp1, :testcomp2, :testcomp3] @test has_comp(my_model.md, :testcomp1) == true @test has_comp(my_model.md, :testcomp4) == false @@ -93,12 +89,6 @@ comps = collect(compdefs(my_model)) add_comp!(my_model, testcomp3, :testcomp3_v2) @test numcomponents(my_model) == 4 -# Test reset_compdefs methods -reset_compdefs() -@test length(_compdefs) == 3 # adder, ConnectorCompVector, ConnectorCompMatrix -reset_compdefs(false) -@test length(_compdefs) == 0 - #------------------------------------------------------------------------------ # Tests for component run periods when resetting the model's time dimension @@ -115,7 +105,7 @@ end # 1. Test resetting the time dimension without explicit first/last values -cd = compdef(testcomp1) +cd = testcomp1 @test cd.first === nothing # original component definition's first and last values are unset @test cd.last === nothing diff --git a/test/test_composite.jl b/test/test_composite.jl index e192d83a8..dead48c15 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -47,9 +47,9 @@ end # Test the calls the macro will produce the following comps = [ - (compdef(Comp1), [:foo => :foo1]), - (compdef(Comp2), [:foo => :foo2]), - (compdef(Comp3), [:foo => :foo3]) + (Comp1, [:foo => :foo1]), + (Comp2, [:foo => :foo2]), + (Comp3, [:foo => :foo3]) ] # TBD: need to implement this to create connections and default value diff --git a/test/test_connectorcomp.jl b/test/test_connectorcomp.jl index 6024391e7..2bf12c712 100644 --- a/test/test_connectorcomp.jl +++ b/test/test_connectorcomp.jl @@ -3,10 +3,7 @@ module TestConnectorComp using Mimi using Test -import Mimi: - reset_compdefs, compdef, compdefs - -reset_compdefs() +import Mimi: compdef, compdefs @defcomp Long begin x = Parameter(index=[time]) @@ -23,7 +20,7 @@ end years = 2000:2010 late_start = 2005 -dim = Mimi.Dimension(years) +year_dim = Mimi.Dimension(years) #------------------------------------------------------------------------------ @@ -50,13 +47,12 @@ x = model1[:Long, :x] @test length(b) == length(years) @test length(x) == length(years) -@test all(ismissing, b[1:dim[late_start]-1]) -@test all(iszero, x[1:dim[late_start]-1]) +@test all(ismissing, b[1:year_dim[late_start]-1]) +@test all(iszero, x[1:year_dim[late_start]-1]) # Test the values are right after the late start -@test b[dim[late_start]:end] == - x[dim[late_start]:end] == - [2 * i for i in 1:(years[end]-late_start + 1)] +@test b[year_dim[late_start]:end] == x[year_dim[late_start]:end] +@test b[year_dim[late_start]:end] == collect(year_dim[late_start]:year_dim[years[end]]) * 2.0 @test Mimi.datum_size(model1.md, Mimi.compdef(model1.md, :Long), :x) == (length(years),) @@ -146,14 +142,15 @@ x = model3[:Long_multi, :x] @test size(b) == (length(years), length(regions)) @test size(x) == (length(years), length(regions)) -@test all(ismissing, b[1:dim[late_start]-1, :]) -@test all(iszero, x[1:dim[late_start]-1, :]) +@test all(ismissing, b[1:year_dim[late_start]-1, :]) +@test all(iszero, x[1:year_dim[late_start]-1, :]) # Test the values are right after the late start -@test b[dim[late_start]:end, :] == - x[dim[late_start]:end, :] == - [[i + 1 for i in 1:(years[end]-late_start + 1)] [i + 2 for i in 1:(years[end]-late_start + 1)]] +late_yr_idxs = year_dim[late_start]:year_dim[end] + +@test b[late_yr_idxs, :] == x[year_dim[late_start]:end, :] +@test b[late_yr_idxs, :] == [[i + 1 for i in late_yr_idxs] [i + 2 for i in late_yr_idxs]] #------------------------------------------------------------------------------ # 4. Test where the short component starts late and ends early @@ -182,16 +179,16 @@ x = model4[:Long_multi, :x] @test size(b) == (length(years), length(regions)) @test size(x) == (length(years), length(regions)) -@test all(ismissing, b[1:dim[first]-1, :]) -@test all(ismissing, b[dim[last]+1:end, :]) -@test all(iszero, x[1:dim[first]-1, :]) -@test all(iszero, x[dim[last]+1:end, :]) +@test all(ismissing, b[1:year_dim[first]-1, :]) +@test all(ismissing, b[year_dim[last]+1:end, :]) +@test all(iszero, x[1:year_dim[first]-1, :]) +@test all(iszero, x[year_dim[last]+1:end, :]) # Test the values are right after the late start -@test b[dim[first]:dim[last], :] == - x[dim[first]:dim[last], :] == - [[i + 1 for i in 1:(years[end]-late_start + 1)] [i + 2 for i in 1:(years[end]-late_start + 1)]] - +yr_idxs = year_dim[first]:year_dim[last] +@test b[yr_idxs, :] == x[yr_idxs, :] +#@test b[yr_idxs, :] == [[i + 1 for i in 1:(years[end]-late_start + 1)] [i + 2 for i in 1:(years[end]-late_start + 1)]] +@test b[yr_idxs, :] == [[i + 1 for i in yr_idxs] [i + 2 for i in yr_idxs]] #------------------------------------------------------------------------------ # 5. Test errors with backup data @@ -229,9 +226,9 @@ end model6 = Model() set_dimension!(model6, :time, years) -add_comp!(model6, foo, :Long) -add_comp!(model6, foo, :Short; first=late_start) -connect_param!(model6, :Short=>:par, :Long=>:var) +add_comp!(model6, foo, :Long; exports=[:var => :long_var]) +add_comp!(model6, foo, :Short; exports=[:var => :short_var], first=late_start) +connect_param!(model6, :Short => :par, :Long => :var) set_param!(model6, :Long, :par, years) run(model6) @@ -244,8 +241,8 @@ short_var = model6[:Short, :var] @test short_par == years # The parameter has values instead of `missing` for years when this component doesn't run, # because they are coming from the longer component that did run -@test all(ismissing, short_var[1:dim[late_start]-1]) -@test short_var[dim[late_start]:end] == years[dim[late_start]:end] +@test all(ismissing, short_var[1:year_dim[late_start]-1]) +@test short_var[year_dim[late_start]:end] == years[year_dim[late_start]:end] end #module diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index 9c5c05ee1..815afb038 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -4,10 +4,7 @@ using Mimi using Test import Mimi: - AbstractDimension, RangeDimension, Dimension, key_type, - reset_compdefs - -reset_compdefs() + compdef, AbstractDimension, RangeDimension, Dimension, key_type, first_period, last_period dim_varargs = Dimension(:foo, :bar, :baz) # varargs dim_vec = Dimension([:foo, :bar, :baz]) # Vector @@ -94,15 +91,17 @@ end m = Model() set_dimension!(m, :time, 2000:2100) @test_throws ErrorException add_comp!(m, foo2; first = 2005, last = 2105) # Can't add a component longer than a model -add_comp!(m, foo2; first = 2005, last = 2095) +foo2_ref = add_comp!(m, foo2; first = 2005, last = 2095) +my_foo2 = compdef(foo2_ref) # Test that foo's time dimension is unchanged @test_logs( (:warn, "Redefining dimension :time"), set_dimension!(m, :time, 1990:2200) ) -@test first_period(compdef(m.md, :foo2)) == 2005 -@test last_period(compdef(m.md, :foo2)) == 2095 + +@test first_period(my_foo2) == 2005 +@test last_period(my_foo2) == 2095 # Test parameter connections @test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long @@ -111,11 +110,13 @@ set_param!(m, :foo2, :x, 2005:2095) # Shouldn't throw an error # Test that foo's time dimension is updated @test_logs( (:warn, "Redefining dimension :time"), - (:warn, "Resetting foo2 component's first timestep to 2010"), - (:warn, "Resetting foo2 component's last timestep to 2050"), + # (:warn, "Resetting foo2 component's first timestep to 2010"), + # (:warn, "Resetting foo2 component's last timestep to 2050"), set_dimension!(m, :time, 2010:2050) ) -@test first_period(compdef(m.md, :foo2)) == 2010 -@test last_period(compdef(m.md, :foo2)) == 2050 + +# TBD: should these be changed as a result of changing model time? +@test first_period(my_foo2) == 2010 +@test last_period(my_foo2) == 2050 end #module diff --git a/test/test_main.jl b/test/test_main.jl index c5c78169f..cc06a080b 100644 --- a/test/test_main.jl +++ b/test/test_main.jl @@ -8,8 +8,6 @@ import Mimi: variable, variable_names, external_param, build, compdefs, dimension, compinstance -reset_compdefs() - @defcomp foo1 begin index1 = Index() @@ -38,8 +36,6 @@ end foo1.par1 = 5.0 end -@test length(compdefs()) == 4 # adder, 2 connectors, and foo1 - # x1 = foo1(Float64, Dict{Symbol, Int}(:time=>10, :index1=>3)) # x1 = foo1(Float64, Val{1}, Val{1}, Val{10}, Val{1}, Val{1}, Val{1}, Val{1}, Dict{Symbol, Int}(:time=>10, :index1=>3)) diff --git a/test/test_main_variabletimestep.jl b/test/test_main_variabletimestep.jl index fa9603cd4..21af883a3 100644 --- a/test/test_main_variabletimestep.jl +++ b/test/test_main_variabletimestep.jl @@ -8,8 +8,6 @@ import Mimi: variable, variable_names, external_param, build, compdef, compdefs, dimension, compinstance -reset_compdefs() - @defcomp foo1 begin index1 = Index() @@ -38,8 +36,6 @@ end # foo1.par2 = [] end -@test length(compdefs()) == 4 # adder, 2 connectors, and foo1 - # x1 = foo1(Float64, Dict{Symbol, Int}(:time=>10, :index1=>3)) # x1 = foo1(Float64, Val{1}, Val{1}, Val{10}, Val{1}, Val{1}, Val{1}, Val{1}, Dict{Symbol, Int}(:time=>10, :index1=>3)) diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 89d96cc20..77b8af540 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -38,8 +38,7 @@ end component(ch4forcing1, ch4forcing2) # add another one with a different name end -# this returns the "registered" version defined by @defcomp -c0 = compdef(ch4forcing1) +c0 = ch4forcing1 @test compmodule(c0) == :TestMetaInfo @test compname(c0) == :ch4forcing1 @test nameof(c0) == :ch4forcing1 @@ -48,8 +47,11 @@ c0 = compdef(ch4forcing1) c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) +@test c1.comp_id == ch4forcing1.comp_id +@test_throws KeyError compdef(test_model, :missingcomp) + @test variable_names(c1) == variable_names(c0) -@test_throws ErrorException compdef(:missingcomp) +@test_throws KeyError compdef(test_model, :missingcomp) @test compmodule(c2) == :TestMetaInfo @test compname(c2) == :ch4forcing1 diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index 65129fbcf..f2f187bb3 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -4,9 +4,7 @@ using Test using Mimi import Mimi: - compdef, reset_compdefs, first_period, last_period, compmodule, compname - -reset_compdefs() + compdef, first_period, last_period, compmodule, compname @defcomp ch4forcing1 begin c_N2Oconcentration = Parameter(index=[time],unit="ppbv") @@ -41,8 +39,10 @@ c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) @test compmodule(c2) == :TestMetaInfo_VariableTimestep -@test compname(c2) == :ch4forcing1 -@test nameof(c2) == :ch4forcing2 + +@test c1.comp_id == ch4forcing1.comp_id +@test c2.comp_id == ch4forcing1.comp_id +@test_throws KeyError compdef(test_model, :missingcomp) vars = Mimi.variable_names(c2) @test length(vars) == 3 diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index f7bb803ba..0ba4599c2 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -131,10 +131,10 @@ add_comp!(m, X, :c2) add_comp!(m, X, :c3) replace_comp!(m, X_repl, :c3) # test replacing the last component -@test comp_id(compdef(m, :c3)) == X_repl +@test compdef(m, :c3).comp_id == X_repl.comp_id replace_comp!(m, X_repl, :c2) # test replacing not the last one -@test comp_id(compdef(m, :c2)) == X_repl +@test compdef(m, :c2).comp_id == X_repl.comp_id end # module \ No newline at end of file diff --git a/test/test_timesteparrays.jl b/test/test_timesteparrays.jl index 94199af48..a2c0fdbbd 100644 --- a/test/test_timesteparrays.jl +++ b/test/test_timesteparrays.jl @@ -240,10 +240,9 @@ m = Model() set_dimension!(m, :time, years) add_comp!(m, foo, :first) add_comp!(m, foo, :second) -connect_param!(m, :second=>:par, :first=>:var) +connect_param!(m, :second => :par, :first => :var) set_param!(m, :first, :par, 1:length(years)) @test_throws MissingException run(m) - end #module diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index cbd0d0c78..1bf3088a4 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -6,11 +6,9 @@ using Test import Mimi: AbstractTimestep, FixedTimestep, VariableTimestep, TimestepVector, TimestepMatrix, TimestepArray, next_timestep, hasvalue, is_first, is_last, - gettime, getproperty, Clock, time_index, get_timestep_array, reset_compdefs, + gettime, getproperty, Clock, time_index, get_timestep_array, is_timestep, is_time -reset_compdefs() - #------------------------------------------------------------------------------ # Test basic timestep functions and Base functions for Fixed Timestep #------------------------------------------------------------------------------ @@ -123,9 +121,10 @@ run(m) @test length(m[:Foo, :output]) == length(years) @test length(m[:Bar, :output]) == length(years) -dim = Mimi.Dimension(years) -foo_output = m[:Foo, :output][dim[first_foo]:dim[years[end]]] -for i in 1:6 +yr_dim = Mimi.Dimension(years) +idxs = yr_dim[first_foo]:yr_dim[years[end]] +foo_output = m[:Foo, :output] +for i in idxs @test foo_output[i] == 5+i end @@ -195,7 +194,7 @@ connect_param!(m2, :Foo2, :inputF, :Bar, :output) run(m2) -foo_output2 = m2[:Foo2, :output][dim[first_foo]:dim[years[end]]] +foo_output2 = m2[:Foo2, :output][yr_dim[first_foo]:yr_dim[years[end]]] for i in 1:6 @test foo_output2[i] == (i+5)^2 end From da767d0bfa7ff5ad98fb229080e3ab37906e7039 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sat, 9 Mar 2019 16:57:17 -0800 Subject: [PATCH 43/81] Add ReshapedDistribution (fixup last merge) --- src/mcs/mcs.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mcs/mcs.jl b/src/mcs/mcs.jl index 2189dc468..f0c281b53 100644 --- a/src/mcs/mcs.jl +++ b/src/mcs/mcs.jl @@ -15,6 +15,6 @@ include("defmcs.jl") export @defsim, generate_trials!, run_sim, save_trial_inputs, save_trial_results, set_models!, - EmpiricalDistribution, RandomVariable, TransformSpec, CorrelationSpec, Simulation, AbstractSimulationData, - LHSData, LatinHypercubeSimulation, MCSData, MonteCarloSimulation, SobolData, SobolSimulation, - INNER, OUTER, sample!, analyze + EmpiricalDistribution, ReshapedDistribution, RandomVariable, TransformSpec, CorrelationSpec, + AbstractSimulationData, Simulation, LHSData, LatinHypercubeSimulation, MCSData, + MonteCarloSimulation, SobolData, SobolSimulation, INNER, OUTER, sample!, analyze From 882f277a84e4c892526a0cc551cf4451f81b8e2d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 25 Mar 2019 09:39:42 -0700 Subject: [PATCH 44/81] WIP - debugging composites --- src/Mimi.jl | 2 +- src/core/build.jl | 4 + src/core/connections.jl | 68 ++++++++++---- src/core/defcomp.jl | 2 +- src/core/defcomposite.jl | 169 ++++++++++++++++------------------- src/core/defs.jl | 53 +++++------ src/core/model.jl | 9 +- src/core/show.jl | 12 ++- src/core/types.jl | 41 ++++++++- src/mcs/defmcs.jl | 1 - src/mcs/montecarlo.jl | 8 +- test/test_components.jl | 3 - test/test_composite.jl | 80 ++++++++++------- test/test_model_structure.jl | 7 +- wip/export_all.jl | 2 +- 15 files changed, 262 insertions(+), 199 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index 899f6a426..fd941a944 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -12,6 +12,7 @@ using StringBuilders export @defcomp, @defsim, + @defcomposite, MarginalModel, Model, add_comp!, @@ -36,7 +37,6 @@ export load_comps, modeldef, name, - new_comp, parameters, parameter_dimensions, parameter_names, diff --git a/src/core/build.jl b/src/core/build.jl index 431e0c643..ec0098491 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -96,6 +96,8 @@ function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dic names = Symbol[] values = Any[] + # @info "_combine_exported_pars: $(comp_def.exports)" + for (name, dr) in comp_def.exports if is_parameter(dr) value = par_dict[(dr.comp_path, dr.name)] @@ -212,11 +214,13 @@ function _build(comp_def::AbstractCompositeComponentDef, end function _build(md::ModelDef) + # @info "_build(md)" # _propagate_exports(md) add_connector_comps(md) # check if all parameters are set not_set = unconnected_params(md) + # @info "not_set: $not_set" if ! isempty(not_set) params = join(not_set, " ") error("Cannot build model; the following parameters are not set: $params") diff --git a/src/core/connections.jl b/src/core/connections.jl index 6059dc637..5411006ca 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -241,35 +241,69 @@ function connect_param!(obj::AbstractCompositeComponentDef, connect_param!(obj, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end +const ParamVector = Vector{ParamPath} + +_collect_connected_params(obj::ComponentDef, connected) = nothing + +function _collect_connected_params(obj::AbstractCompositeComponentDef, connected::ParamVector) + for comp_def in compdefs(obj) + _collect_connected_params(comp_def, connected) + end + + ext_set_params = map(x->(x.comp_path, x.param_name), external_param_conns(obj)) + int_set_params = map(x->(x.dst_comp_path, x.dst_par_name), internal_param_conns(obj)) + + append!(connected, union(ext_set_params, int_set_params)) +end + +function connected_params(md::ModelDef) + connected = ParamVector() + _collect_connected_params(md, connected) + return connected +end + """ connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) Return list of parameters that have been set for component `comp_name` in composite `obj`. """ -function connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) - ext_set_params = map(x->x.param_name, external_param_conns(obj, comp_name)) - int_set_params = map(x->x.dst_par_name, internal_param_conns(obj, comp_name)) +# Deprecated +# function connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) +# ext_set_params = map(x->x.param_name, external_param_conns(obj, comp_name)) +# int_set_params = map(x->x.dst_par_name, internal_param_conns(obj, comp_name)) - return union(ext_set_params, int_set_params) -end +# return union(ext_set_params, int_set_params) +# end """ - unconnected_params(obj::AbstractCompositeComponentDef) - -Return a list of tuples (comp_path, parame_name) of parameters -that have not been connected to a value in the composite `obj`. +Depth-first search for unconnected parameters, which are appended to `unconnected`. Parameter +connections are made to the "original" component, not to a composite that exports the parameter. +Thus, only the leaf (non-composite) variant of this method actually collects uncollected params. """ -function unconnected_params(obj::AbstractCompositeComponentDef) - unconnected = Vector{Tuple{ComponentPath,Symbol}}() - +function _collect_unconnected_params(obj::ComponentDef, connected::ParamVector, unconnected::ParamVector) + comp_path = obj.comp_path + params = map(x->(comp_path, x), parameter_names(obj)) + append!(unconnected, setdiff(params, connected)) +end + +function _collect_unconnected_params(obj::AbstractCompositeComponentDef, connected::ParamVector, unconnected::ParamVector) for comp_def in compdefs(obj) - comp_path = comp_def.comp_path - params = parameter_names(comp_def) - connected = connected_params(obj, nameof(comp_def)) - append!(unconnected, map(x->(comp_path, x), setdiff(params, connected))) + _collect_unconnected_params(comp_def, connected, unconnected) end +end + +""" + unconnected_params(md::ModelDef) + +Return a list of tuples (comp_path, param_name) of parameters +that have not been connected to a value anywhere in `md`. +""" +function unconnected_params(md::ModelDef) + unconnected = ParamVector() + connected = connected_params(md) - return unconnected + _collect_unconnected_params(md, connected, unconnected) + return unconnected end """ diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 9c09549f4..d8295a7bf 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -240,7 +240,7 @@ macro defcomp(comp_name, ex) end end - vartype = (vartype === nothing ? Number : Base.eval(Main, vartype)) + vartype = (vartype === nothing ? Number : getproperty(Main, vartype)) addexpr(_generate_var_or_param(elt_type, name, vartype, dimensions, dflt, desc, unit)) else diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index b9c2c06e2..676ab1b2a 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -6,47 +6,87 @@ _arg_type(arg_tup) = arg_tup[2] _arg_slurp(arg_tup) = arg_tup[3] _arg_default(arg_tup) = arg_tup[4] -function _extract_args(args, kwargs) - valid_kws = (:exports, :bindings) # valid keyword args to `component()` - kw_values = Dict() +function _collect_exports(exprs) + # each item in exprs is either a single symbol, or an expression mapping + # one symbol to another, e.g., [:foo, :bar, :(:baz => :my_baz)]. We peel + # out the symbols to create a list of pairs. + exports = [] + # @info "_collect_exports: $exprs" + + for expr in exprs + if (@capture(expr, name_ => expname_) || @capture(expr, name_)) && + (name isa Symbol && (expname === nothing || expname isa Symbol)) + push!(exports, name => @or(expname, name)) + else + error("Elements of exports list must Symbols or Pair{Symbol, Symbol}, got $expr") + end + end + + # @info "returning $exports" + return exports +end + +const NumericArray = Array{T, N} where {T <: Number, N} + +function _collect_bindings(exprs) + bindings = [] + # @info "_collect_bindings: $exprs" + + for expr in exprs + if @capture(expr, name_ => val_) && name isa Symbol && + (val isa Symbol || val isa Number || val.head in (:vcat, :hcat, :vect)) + push!(bindings, name => val) + else + error("Elements of bindings list must Pair{Symbol, Symbol} or Pair{Symbol, Number or Array of Number} got $expr") + end + end + + # @info "returning $bindings" + return bindings +end +function _subcomp(args, kwargs) + # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) arg_tups = map(splitarg, args) if kwargs === nothing - # If a ";" was not used to separate kwargs, extract them from args. - # tup[4] => "default" value which for kwargs, the actual value. - kwarg_tups = filter!(tup -> _arg_default(tup) !== nothing, arg_tups) + # If a ";" was not used to separate kwargs, move any kwargs from args. + kwarg_tups = filter(tup -> _arg_default(tup) !== nothing, arg_tups) + arg_tups = filter(tup -> _arg_default(tup) === nothing, arg_tups) else kwarg_tups = map(splitarg, kwargs) end - @info "args: $arg_tups" - @info "kwargs: $kwarg_tups" - if 1 > length(arg_tups) > 2 @error "component() must have one or two non-keyword values" end arg1 = _arg_name(arg_tups[1]) - arg2 = length(args) == 2 ? _arg_name(arg_tups[2]) : nothing + alias = length(arg_tups) == 2 ? _arg_name(args_tups[2]) : nothing + + cmodule = nothing + if ! (@capture(arg1, cmodule_.cname_) || @capture(arg1, cname_Symbol)) + error("Component name must be a Module.name expression or a symbol, got $arg1") + end - for tup in kwarg_tups - arg_name = _arg_name(tup) + valid_kws = (:exports, :bindings) # valid keyword args to the component() psuedo-function + kw = Dict([key => [] for key in valid_kws]) + + for (arg_name, arg_type, slurp, default) in kwarg_tups if arg_name in valid_kws - default = _arg_default(tup) - if hasmethod(Base.iterate, (typeof(default),)) - append!(kw_values[arg_name], default) + if default isa Expr && hasmethod(Base.iterate, (typeof(default.args),)) + append!(kw[arg_name], default.args) else @error "Value of $arg_name argument must be iterable" end - else @error "Unknown keyword $arg_name; valid keywords are $valid_kws" end end - @info "kw_values: $kw_values" - return (arg1, arg2, kw_values) + exports = _collect_exports(kw[:exports]) + bindings = _collect_bindings(kw[:bindings]) + return SubComponent(cmodule, cname, alias, exports, bindings) end """ @@ -56,12 +96,13 @@ Define a Mimi CompositeComponent `cc_name` with the expressions in `ex`. Expres are all variations on `component(...)`, which adds a component to the composite. The calling signature for `component()` processed herein is: - `component(comp_id::ComponentId, name::Symbol=comp_id.comp_name; - exports::Union{Nothing,ExportsDict}, bindings::Union{Nothing,Vector{Binding}})` + component(comp_name, local_name; + exports=[list of symbols or Pair{Symbol,Symbol}], + bindings=[list Pair{Symbol, Symbol or Number or Array of Numbers}]) In this macro, the vector of symbols to export is expressed without the `:`, e.g., -`exports=[var_1, var_2, param_1])`. The names must be variable or parameter names in -the component being added. +`exports=[var_1, var_2 => export_name, param_1])`. The names must be variable or +parameter names exported to the composite component being added by its sub-components. Bindings are expressed as a vector of `Pair` objects, where the first element of the pair is the name (again, without the `:` prefix) representing a parameter in the component @@ -72,86 +113,28 @@ followed by a `.` and the variable name in that component. So the form is either `modname.compname.varname` or `compname.varname`, which must be known in the current module. Unlike leaf components, composite components do not have user-defined `init` or `run_timestep` -functions; these are defined internally to iterate over constituent components and call -the associated method on each. +functions; these are defined internally to iterate over constituent components and call the +associated method on each. """ macro defcomposite(cc_name, ex) @capture(ex, elements__) - - result = :( - # @__MODULE__ is evaluated in calling module when macro is interpreted - let calling_module = @__MODULE__ # Use __module__? - global $cc_name = CompositeComponentDef(calling_module) - end - ) - - # helper function used in loop below - function addexpr(expr) - let_block = result.args[end].args - push!(let_block, expr) - end - - valid_kws = (:exports, :bindings) # valid keyword args to `component()` - kw_values = Dict() + comps = SubComponent[] for elt in elements - offset = 0 - if @capture(elt, (component(args__; kwargs__) | component(args__))) - - # component(comp_mod_name_.comp_name_) | - # component(comp_mod_name_.comp_name_, alias_))) - - # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) - arg_tups = map(splitarg, args) - - if kwargs === nothing - # If a ";" was not used to separate kwargs, extract them from args. - # tup[4] => "default" value which for kwargs, the actual value. - kwarg_tups = filter!(tup -> tup[4] !== nothing, arg_tups) - else - kwarg_tups = map(splitarg, kwargs) - end - - @info "args: $args" - @info "kwargs: $kwargs" - - if 1 > length(args) > 2 - @error "component() must have one or two non-keyword values" - end - - arg1 = args[1] - arg2 = length(args) == 2 ? args[2] : nothing - - for (arg_name, arg_type, slurp, default) in kwarg_tups - if arg_name in valid_kws - if hasmethod(Base.iterate, typeof(default)) - append!(kw_values[arg_name], default) - else - @error "Value of $arg_name argument must be iterable" - end - - else - @error "Unknown keyword $arg_name; valid keywords are $valid_kws" - end - end - - @info "kw_values: $kw_values" - - # set local copy of comp_mod_name to the stated or default component module - expr = (comp_mod_name === nothing ? :(comp_mod_name = nameof(calling_module)) : :(comp_mod_name = $(QuoteNode(comp_mod_name)))) - addexpr(expr) - - # name = (alias === nothing ? comp_name : alias) - # expr = :(add_comp!($cc_name, eval(comp_mod_name).$comp_name, $(QuoteNode(name)))) - - expr = :(CompositeComponentDef($comp_id, $comp_name, $comps; bindings=$bindings, exports=$exports)) - addexpr(expr) + push!(comps, _subcomp(args, kwargs)) + else + error("Unrecognized element in @defcomposite: $elt") end end + result = quote + cc_id = Mimi.ComponentId(nameof(@__MODULE__), $(QuoteNode(cc_name))) + global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, @__MODULE__) + $cc_name + end - # addexpr(:($cc_name)) # return this or nothing? - addexpr(:(nothing)) return esc(result) end + +nothing diff --git a/src/core/defs.jl b/src/core/defs.jl index 8e7214f83..d7828fe1b 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -387,6 +387,7 @@ function parameters(ccd::AbstractCompositeComponentDef) # return cached parameters, if any if length(pars) == 0 for (name, dr) in ccd.exports + @info "dr: $dr" cd = compdef(dr) if has_parameter(cd, nameof(dr)) pars[name] = parameter(cd, nameof(dr)) @@ -463,6 +464,7 @@ end function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) comp = find_comp(obj, comp_path, relative=false) + @or(comp, error("Component with path $comp_path not found")) set_param!(comp.parent, nameof(comp), param_name, value, dims) end @@ -780,13 +782,6 @@ function _propagate_time(obj::AbstractComponentDef, t::Dimension) end end -""" - printable(value) - -Return a value that is not nothing. -""" -printable(value) = (value === nothing ? ":nothing:" : value) - function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol, datum_name::Symbol) path = ComponentPath(parent.comp_path, comp_name) @@ -854,32 +849,30 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone obj.exports[export_name] = _find_var_par(obj, comp_def, comp_name, name) end - # check that a time dimension has been set - if ! has_dim(obj, :time) - error("Cannot add component to composite without first setting time dimension.") + # Check if component being added already exists + if has_comp(obj, comp_name) + error("Cannot add two components of the same name ($comp_name)") end - # check that first and last are within the model's time index range - time_index = dim_keys(obj, :time) - - if first !== nothing && first < time_index[1] - error("Cannot add component $comp_name with first time before first of model's time index range.") - end + # check that a time dimension has been set + if has_dim(obj, :time) + # error("Cannot add component to composite without first setting time dimension.") + + # check that first and last are within the model's time index range + time_index = dim_keys(obj, :time) - if last !== nothing && last > time_index[end] - error("Cannot add component $comp_name with last time after end of model's time index range.") - end + if first !== nothing && first < time_index[1] + error("Cannot add component $comp_name with first time before first of model's time index range.") + end - if before !== nothing && after !== nothing - error("Cannot specify both 'before' and 'after' parameters") - end + if last !== nothing && last > time_index[end] + error("Cannot add component $comp_name with last time after end of model's time index range.") + end - # Check if component being added already exists - if has_comp(obj, comp_name) - error("Cannot add two components of the same name ($comp_name)") - end + if before !== nothing && after !== nothing + error("Cannot specify both 'before' and 'after' parameters") + end - if has_dim(obj, :time) _propagate_time(comp_def, dimension(obj, :time)) end @@ -1021,8 +1014,10 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, return add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end -function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) - if relative +function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) + @info "find_comp($(obj.name), $path; relative=$relative)" + @info "obj.parent = $(printable(obj.parent))" + if (relative || obj.parent === nothing) if isempty(path) return obj end diff --git a/src/core/model.jl b/src/core/model.jl index 52f63a99b..f25f923a8 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -27,7 +27,7 @@ is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate external_params(m::Model) => md @delegate external_param(m::Model, name::Symbol; missing_ok=false) => md -@delegate connected_params(m::Model, comp_name::Symbol) => md +@delegate connected_params(m::Model) => md @delegate unconnected_params(m::Model) => md @delegate add_connector_comps(m::Model) => md @@ -128,13 +128,6 @@ function add_comp!(m::Model, comp_def::ComponentDef, comp_name::Symbol=comp_def. return add_comp!(m, comp_def.comp_id, comp_name; kwargs...) end -function add_comp!(m::Model, comp_def::ComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; - first=nothing, last=nothing, before=nothing, after=nothing) - add_comp!(m.md, comp_def, comp_name; first=first, last=last, before=before, after=after) - decache(m) - return ComponentReference(m, comp_name) -end - """ replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingSymbol=nothing, last::NothingSymbol=nothing, diff --git a/src/core/show.jl b/src/core/show.jl index 3e8820c16..ba7063f25 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -15,6 +15,13 @@ end indent(io::IO) = _indent_level!(io, 1) +""" + printable(value) + +Return a value that is not nothing. +""" +printable(value) = (value === nothing ? ":nothing:" : value) + function print_indented(io::IO, args...) level = get(io, :indent_level, 0) print(io, repeat(spaces, level), args...) @@ -48,6 +55,9 @@ function _show_field(io::IO, name::Symbol, dict::AbstractDict; show_empty=true) end function _show_field(io::IO, name::Symbol, vec::Vector{T}; show_empty=true) where T + print(io, "\n") + print_indented(io, name, ": ", typeof(vec)) + count = length(vec) elide = false max_shown = 5 @@ -152,7 +162,7 @@ function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) pos = findfirst(x -> x == field, fields) if pos !== nothing value = getfield(obj, field) - name = (value === nothing ? "nothing" : nameof(value)) + name = printable(value === nothing ? nothing : nameof(value)) fields = deleteat!([fields...], pos) print(io, "\n") print_indented(indent(io), "$field: $(typeof(value))($name)") diff --git a/src/core/types.jl b/src/core/types.jl index 5df2f3211..ba8843170 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,6 +1,15 @@ using Classes using DataStructures +""" + @or(args...) + +Return the first argument whose value is not `nothing` +""" +macro or(a, b) + esc(:($a === nothing ? $b : $a)) +end + # Having all our structs/classes subtype these simplifies "show" methods abstract type MimiStruct end @class MimiClass <: Class @@ -20,6 +29,8 @@ struct ComponentPath <: MimiStruct names::NTuple{N, Symbol} where N end +const ParamPath = Tuple{ComponentPath, Symbol} + ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) @@ -262,10 +273,10 @@ end name::Union{Nothing, Symbol}=nothing) if comp_id === nothing # ModelDefs are anonymous, but since they're gensym'd, they can claim the Mimi package - comp_id = ComponentId(Mimi, name === nothing ? gensym(nameof(typeof(self))) : name) + comp_id = ComponentId(Mimi, @or(name, gensym(nameof(typeof(self))))) end - name = (name === nothing ? comp_id.comp_name : name) + name = @or(name, comp_id.comp_name) NamedObj(self, name) self.comp_id = comp_id @@ -303,6 +314,14 @@ end @class ParameterDefReference <: DatumReference @class VariableDefReference <: DatumReference +# Used by @defcomposite to communicate subcomponent information +struct SubComponent <: MimiStruct + module_name::Union{Nothing, Symbol} + comp_name::Symbol + alias::Union{Nothing, Symbol} + exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} + bindings::Vector{Pair{Symbol, Any}} +end # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} @@ -342,6 +361,20 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} end end +# Used by @defcomposite +function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Vector{SubComponent}, + calling_module::Module) + # @info "CompositeComponentDef($comp_id, $alias, $subcomps)" + composite = CompositeComponentDef(comp_id) + + for c in subcomps + subcomp_id = ComponentId(@or(c.module_name, calling_module), c.comp_name) + subcomp = compdef(subcomp_id) + add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) + end + return composite +end + # TBD: these should dynamically and recursively compute the lists internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns @@ -462,8 +495,8 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro self.parameters = pars # If first or last is `nothing`, substitute first or last time period - self.first = comp_def.first !== nothing ? comp_def.first : time_bounds[1] - self.last = comp_def.last !== nothing ? comp_def.last : time_bounds[2] + self.first = @or(comp_def.first, time_bounds[1]) + self.last = @or(comp_def.last, time_bounds[2]) # @info "ComponentInstance evaluating $(comp_id.module_name)" module_name = comp_id.module_name diff --git a/src/mcs/defmcs.jl b/src/mcs/defmcs.jl index ac3c06e54..dd5672d87 100644 --- a/src/mcs/defmcs.jl +++ b/src/mcs/defmcs.jl @@ -162,7 +162,6 @@ macro defsim(expr) # call constructor on given args data = :($simdatatype(; $(_sim_args)...)) - # TBD: need to generalize this to support other methods return :(Simulation{$simdatatype}( [$(_rvs...)], [$(_transforms...)], diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index 19604694f..f07cf3a17 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -151,8 +151,6 @@ function generate_trials!(sim::Simulation{T}, samplesize::Int; sample!(sim, samplesize) - # TBD: If user asks for trial data to be saved, generate it up-front, or - # open a file that can be written to for each trialnum/scenario set? if filename != "" save_trial_inputs(sim, filename) end @@ -188,9 +186,9 @@ is necessary when we are applying distributions by adding or multiplying origina function _copy_sim_params(sim::Simulation{T}) where T <: AbstractSimulationData param_vec = Vector{Dict{Symbol, ModelParameter}}(undef, length(sim.models)) - for (i, m) in enumerate(mcs.models) + for (i, m) in enumerate(sim.models) md = modelinstance_def(m) - param_vec[i] = Dict{Symbol, ModelParameter}(trans.paramname => deepcopy(external_param(md, trans.paramname)) for trans in mcs.translist) + param_vec[i] = Dict{Symbol, ModelParameter}(trans.paramname => deepcopy(external_param(md, trans.paramname)) for trans in sim.translist) end return param_vec @@ -381,7 +379,7 @@ function run_sim(sim::Simulation{T}, error("run_sim: scenario_func and scenario_arg must both be nothing or both set to non-nothing values") end - for m in mcs.models + for m in sim.models if ! is_built(m) build(m) end diff --git a/test/test_components.jl b/test/test_components.jl index 0817ac9d5..99db900b6 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -43,9 +43,6 @@ end end end -# Can't add component before setting time dimension -@test_throws ErrorException add_comp!(my_model, testcomp1) - # Start building up the model set_dimension!(my_model, :time, 2015:5:2110) add_comp!(my_model, testcomp1) diff --git a/test/test_composite.jl b/test/test_composite.jl index dead48c15..b2609ad88 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -5,9 +5,7 @@ using Mimi import Mimi: ComponentId, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, - Binding, ExportsDict, ModelDef, build, time_labels, reset_compdefs, compdef - -reset_compdefs() + Binding, ExportsDict, ModelDef, build, time_labels, compdef @defcomp Comp1 begin @@ -44,45 +42,65 @@ end end end +global m = Model() +set_dimension!(m, :time, 2005:2020) +md = m.md -# Test the calls the macro will produce the following -comps = [ - (Comp1, [:foo => :foo1]), - (Comp2, [:foo => :foo2]), - (Comp3, [:foo => :foo3]) -] +LONG_WAY = false -# TBD: need to implement this to create connections and default value -bindings = Binding[] - # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 - # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) -# ] +if LONG_WAY -exports = [] - # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - # DatumReference(:par_2_2, Comp2) => :c2p2, - # DatumReference(:var_3_1, Comp3) => :c3v1 -# ] + # Test the calls the macro will produce the following + comps = [ + (Comp1, [:foo => :foo1]), + (Comp2, [:foo => :foo2]), + (Comp3, [:foo => :foo3]) + ] -compos_name = :top -compos_id = ComponentId(:TestComposite, compos_name) -compos = CompositeComponentDef(compos_id) -# CompositeComponentDef(compos, ccid) # , comps, bindings, exports) + # TBD: need to implement this to create connections and default value + bindings = Binding[] + # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 + # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 + # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) + # ] -global m = Model() -set_dimension!(m, :time, 2005:2020) -md = m.md -top = add_comp!(md, compos, nameof(compos)) # add top-level composite under model def to test 2-layer model + exports = [] + # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 + # DatumReference(:par_2_2, Comp2) => :c2p2, + # DatumReference(:var_3_1, Comp3) => :c3v1 + # ] -# Add components to composite -for (c, exports) in comps - add_comp!(top, c, nameof(c), exports=exports) # later allow pair for renaming + compos_name = :top + compos_id = ComponentId(:TestComposite, compos_name) + compos = CompositeComponentDef(compos_id) + + top = add_comp!(md, compos, nameof(compos)) # add top-level composite under model def to test 2-layer model + + # Add components to composite + for (c, exports) in comps + add_comp!(top, c, nameof(c), exports=exports) # later allow pair for renaming + end +else + exports = [] # TBD: what to export from the composite + bindings = Binding[] + + @defcomposite top begin + component(Comp1; exports=[foo => foo1]) + component(Comp2, exports=[foo => foo2]) + component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) + # exports(list of names or pairs to export) + end + + add_comp!(md, top, nameof(top)) end merge!(md.exports, ExportsDict(exports)) append!(md.bindings, bindings) +set_param!(m, "/top/Comp1", :foo, 10) +set_param!(m, "/top/Comp2", :foo, 20) +set_param!(m, "/top/Comp3", :foo, 30) + set_param!(m, "/top/Comp1", :par_1_1, zeros(length(time_labels(md)))) connect_param!(top, :Comp2, :par_2_1, :Comp1, :var_1_1) diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 6e107241d..9e110eed5 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -7,11 +7,9 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, - reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, + numcomponents, get_connections, internal_param_conns, dim_count, dim_names, modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs, comp_path -reset_compdefs() - @defcomp A begin varA::Int = Variable(index=[time]) parA::Int = Parameter() @@ -44,8 +42,9 @@ end m = Model() +# TBD: This is not necessarily an error with composites. # make sure you can't add a component before setting time dimension -@test_throws ErrorException add_comp!(m, A) +# @test_throws ErrorException add_comp!(m, A) set_dimension!(m, :time, 2015:5:2100) diff --git a/wip/export_all.jl b/wip/export_all.jl index f3541d046..ac2885038 100644 --- a/wip/export_all.jl +++ b/wip/export_all.jl @@ -6,7 +6,7 @@ macro import_all(pkg) ! (symbol in (:eval, :show, :include, :name) || string(symbol)[1] == '#') end - symbols = Iterators.filter(ok_to_import, names(eval(pkg), all=true)) + symbols = Iterators.filter(ok_to_import, names(getproperty(@__MODULE__, pkg), all=true)) symlist = join(symbols, ",") return Meta.parse("import $pkg: $symlist") end From 50b9db65b5a0fb9098014fc397a4bff05370c208 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 26 Mar 2019 16:50:05 -0700 Subject: [PATCH 45/81] WIP -- first working version of @defcomposite. Still missing the "bindings" feature, though. All tests pass. --- src/core/build.jl | 2 + src/core/defcomp.jl | 2 +- src/core/defs.jl | 280 ++++++++++++++++++++++------------------- src/core/model.jl | 76 +++++------ src/core/types.jl | 49 +------- test/test_composite.jl | 76 +++-------- 6 files changed, 212 insertions(+), 273 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index ec0098491..60129e8a3 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -79,6 +79,8 @@ function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dic values = Any[] for (name, dr) in comp_def.exports + root = dr.root === nothing ? nothing : dr.root.comp_id + # @info "dr.root: $(printable(root)), comp_path: $(printable(dr.comp_path))" if is_variable(dr) obj = var_dict[dr.comp_path] value = getproperty(obj, nameof(dr)) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index d8295a7bf..6fdb47007 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -240,7 +240,7 @@ macro defcomp(comp_name, ex) end end - vartype = (vartype === nothing ? Number : getproperty(Main, vartype)) + vartype = (vartype === nothing ? Number : Main.eval(vartype)) addexpr(_generate_var_or_param(elt_type, name, vartype, dimensions, dflt, desc, unit)) else diff --git a/src/core/defs.jl b/src/core/defs.jl index d7828fe1b..fe7a2cab4 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,17 +1,16 @@ compdef(comp_id::ComponentId) = getfield(getfield(Main, comp_id.module_name), comp_id.comp_name) -compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path, relative=false) +compdef(cr::ComponentReference) = find_comp(cr.parent, cr.comp_path; relative=false) -compdef(dr::AbstractDatumReference) = compdef(dr.root, dr.comp_path) -compdef(cr::ComponentReference) = compdef(cr.parent, cr.comp_path) - -# Allows method to be called on leaf component defs, which sometimes simplifies code. -compdefs(c::ComponentDef) = [] +compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path; relative=false) +compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.comps_dict[comp_name] compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) compkeys(c::AbstractCompositeComponentDef) = keys(c.comps_dict) has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) -compdef(c::AbstractCompositeComponentDef, comp_name::Symbol) = c.comps_dict[comp_name] + +# Allows method to be called on leaf component defs, which sometimes simplifies code. +compdefs(c::ComponentDef) = [] compmodule(comp_id::ComponentId) = comp_id.module_name compname(comp_id::ComponentId) = comp_id.comp_name @@ -27,7 +26,7 @@ Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict # TBD: deprecated function reset_compdefs(reload_builtins=true) if reload_builtins - compdir = joinpath(@__DIR__, "..", "components") + compdir = joinpath(@__DIR__, "..", "components") load_comps(compdir) end end @@ -37,11 +36,13 @@ function comp_path!(parent::AbstractCompositeComponentDef, child::AbstractCompon end """ - comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) -Convert a string describing a path from a node to a ComponentPath. +Convert a string describing a path from a node to a ComponentPath. The validity +of the path is not checked. If `path` starts with "/", the first element in the +returned component path is set to the root of the hierarchy containing `node`. """ -function comp_path(node::AbstractCompositeComponentDef, path::AbstractString) +function _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) # empty path means just select the node's path isempty(path) && return node.comp_path @@ -54,6 +55,71 @@ function comp_path(node::AbstractCompositeComponentDef, path::AbstractString) return ComponentPath([Symbol(elt) for elt in elts]) end +function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) + # @info "find_comp($(obj.name), $path; relative=$relative)" + # @info "obj.parent = $(printable(obj.parent))" + + # Convert "absolute" path from a root node to relative + if ! relative + relative = true + path = rel_path(obj.comp_path, path) + end + + if isempty(path) + return obj + end + + names = path.names + if has_comp(obj, names[1]) + return find_comp(compdef(obj, names[1]), ComponentPath(names[2:end])) + end + + return nothing +end + +find_comp(obj::AbstractComponentDef, name::Symbol) = compdef(obj, name) + +find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) + +function find_comp(obj::AbstractCompositeComponentDef, pathstr::AbstractString) + path = _comp_path(obj, pathstr) + relative = (isempty(pathstr) || pathstr[1] != '/') + find_comp(obj, path, relative=relative) +end + +find_comp(dr::AbstractDatumReference; relative=false) = find_comp(dr.root, dr.comp_path; relative=relative) + +""" +Return the relative path of `descendant` if is within the path of composite `ancestor` or +or nothing otherwise. +""" +function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) + a_names = ancestor_path.names + d_names = descendant_path.names + + if ((a_len = length(a_names)) >= (d_len = length(d_names)) || d_names[1:a_len] != a_names) + # @info "rel_path($a_names, $d_names) returning nothing" + return nothing + end + + return ComponentPath(d_names[a_len+1:end]) +end + +""" +Return whether component `descendant` is within the composite structure of `ancestor` or +any of its descendants. If the comp_paths check out, the node is located within the +structure to ensure that the component is really where it says it is. (Trust but verify!) +""" +function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) + a_path = ancestor.comp_path + d_path = descendant.comp_path + if (relpath = rel_path(a_path, d_path)) === nothing + return false + end + + return find_comp(ancestor, relpath; relative=true) +end + dirty(md::ModelDef) = md.dirty function dirty!(obj::AbstractComponentDef) @@ -62,7 +128,7 @@ function dirty!(obj::AbstractComponentDef) return end - if root isa ModelDef + if root isa ModelDef dirty!(root) end end @@ -103,11 +169,17 @@ compname(dr::AbstractDatumReference) = dr.comp_path.names[end] is_variable(dr::AbstractDatumReference) = false is_parameter(dr::AbstractDatumReference) = false -is_variable(dr::VariableDefReference) = has_variable(compdef(dr), nameof(dr)) -is_parameter(dr::ParameterDefReference) = has_parameter(compdef(dr), nameof(dr)) +is_variable(dr::VariableDefReference) = has_variable(find_comp(dr), nameof(dr)) +is_parameter(dr::ParameterDefReference) = has_parameter(find_comp(dr), nameof(dr)) number_type(md::ModelDef) = md.number_type -number_type(obj::AbstractCompositeComponentDef) = number_type(get_root(obj)) + +function number_type(obj::AbstractCompositeComponentDef) + root = get_root(obj) + # TBD: hack alert. Need to allow number_type to be specified + # for composites that are not yet connected to a ModelDef? + return root isa ModelDef ? number_type(md) : Float64 +end # TBD: should be numcomps() numcomponents(obj::AbstractComponentDef) = 0 # no sub-components @@ -125,7 +197,7 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) comp_def = compdef(ccd, comp_name) delete!(ccd.comps_dict, comp_name) - + # Remove references to the deleted comp comp_path = comp_def.comp_path exports = ccd.exports @@ -142,7 +214,7 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) filter!(ipc_filter, ccd.internal_param_conns) epc_filter = x -> x.comp_path != comp_path - filter!(epc_filter, ccd.external_param_conns) + filter!(epc_filter, ccd.external_param_conns) end # @@ -261,7 +333,7 @@ function _show_run_period(obj::AbstractComponentDef, first, last) @info "Setting run period for $which $(nameof(obj)) to ($first, $last)" end -""" +""" set_run_period!(obj::ComponentDef, first, last) Allows user to narrow the bounds on the time dimension. @@ -277,15 +349,15 @@ function set_run_period!(obj::AbstractComponentDef, first, last) changed = false if first !== nothing - if first_per !== nothing && first_per < first + if first_per !== nothing && first_per < first @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" end obj.first = first changed = true - end + end if last !== nothing - if last_per !== nothing && last_per > last + if last_per !== nothing && last_per > last @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" end obj.last = last @@ -300,12 +372,12 @@ function set_run_period!(obj::AbstractComponentDef, first, last) for subcomp in compdefs(obj) set_run_period!(subcomp, first, last) end - + nothing end - + """ - set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) Set the values of `ccd` dimension `name` to integers 1 through `count`, if `keys` is an integer; or to the values in the vector or range if `keys` is either of those types. @@ -320,14 +392,14 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: set_uniform!(ccd, isuniform(keys)) #set_run_period!(ccd, keys[1], keys[end]) end - + return set_dimension!(ccd, name, Dimension(keys)) end function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) dirty!(obj) obj.dim_dict[name] = dim - + if name == :time for subcomp in compdefs(obj) set_dimension!(subcomp, :time, dim) @@ -336,17 +408,17 @@ function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) return dim end -# helper functions used to determine if the provided time values are +# helper functions used to determine if the provided time values are # a uniform range. function all_equal(values) return all(map(val -> val == values[1], values[2:end])) end - + function isuniform(values) if length(values) == 0 return false - else + else return all_equal(diff(collect(values))) end end @@ -356,7 +428,7 @@ function isuniform(values::Int) return true end -# +# # Parameters # @@ -387,15 +459,15 @@ function parameters(ccd::AbstractCompositeComponentDef) # return cached parameters, if any if length(pars) == 0 for (name, dr) in ccd.exports - @info "dr: $dr" - cd = compdef(dr) + cd = find_comp(dr; relative=true) + if has_parameter(cd, nameof(dr)) pars[name] = parameter(cd, nameof(dr)) end end end - return values(pars) + return values(pars) end """ @@ -476,15 +548,15 @@ component with name `:x` beneath the root of the hierarchy in which `obj` is fou not begin with "/", it is treated as relative to `obj`. """ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) - set_param!(obj, comp_path(obj, path), param_name, value, dims) + set_param!(obj, _comp_path(obj, path), param_name, value, dims) end """ set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, name::Symbol, value, dims=nothing) Set the parameter `name` of a component `comp_name` in a composite `obj` to a given `value`. The -`value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a -list of the dimension names of the provided data, and will be used to check that +`value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a +list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) @@ -499,7 +571,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param comp_param_dims = parameter_dimensions(obj, comp_name, param_name) num_dims = length(comp_param_dims) - + comp_def = compdef(obj, comp_name) param = parameter(comp_def, param_name) data_type = param.datatype @@ -515,7 +587,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param value_dims = length(size(value)) if num_dims != value_dims error("Mismatched data size for a set parameter call: dimension :$param_name in $(comp_name) has $num_dims dimensions; indicated value has $value_dims dimensions.") - end + end value = convert(Array{dtype, num_dims}, value) end @@ -532,11 +604,11 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param stepsize = step_size(obj) values = TimestepArray{FixedTimestep{first, stepsize}, T, num_dims}(value) else - times = time_labels(obj) - #use the first from the comp_def - first_index = findfirst(isequal(first), times) + times = time_labels(obj) + #use the first from the comp_def + first_index = findfirst(isequal(first), times) values = TimestepArray{VariableTimestep{(times[first_index:end]...,)}, T, num_dims}(value) - end + end end else values = value @@ -569,7 +641,7 @@ function variables(ccd::AbstractCompositeComponentDef) cd = compdef(dr.comp_id) if has_variable(cd, nameof(dr)) vars[name] = variable(cd, nameof(dr)) - end + end end end @@ -640,7 +712,7 @@ function variable_dimensions(obj::AbstractComponentDef, name::Symbol) end -# Add a variable to a ComponentDef. CompositeComponents have no vars of their own, +# Add a variable to a ComponentDef. CompositeComponents have no vars of their own, # only references to vars in components contained within. function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) var_def = VariableDef(name, datatype, dimensions, description, unit) @@ -782,11 +854,13 @@ function _propagate_time(obj::AbstractComponentDef, t::Dimension) end end -function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, +function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol, datum_name::Symbol) path = ComponentPath(parent.comp_path, comp_name) root = get_root(parent) + root === nothing && error("Component $(parent.comp_id) does have a root") + # @info "comp path: $path, datum_name: $datum_name" # for composites, check that the named vars/pars are exported? @@ -804,24 +878,24 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract end """ - add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, - comp_name::Symbol=comp_def.comp_id.comp_name; + add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, + comp_name::Symbol=comp_def.comp_id.comp_name; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component +Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. Note that a copy of `comp_def` is created and inserted into the composite under the given `comp_name`. -The `exports` arg identifies which vars/pars to make visible to the next higher composite level, and with +The `exports` arg identifies which vars/pars to make visible to the next higher composite level, and with what names. If `nothing`, everything is exported. The first element of a pair indicates the symbol to export -from comp_def to the composite, the second element allows this var/par to have a new name in the composite. +from comp_def to the composite, the second element allows this var/par to have a new name in the composite. A symbol alone means to use the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] """ -function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, +function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; exports=nothing, - first::NothingInt=nothing, last::NothingInt=nothing, + first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) - + # If not specified, export all var/pars. Caller can pass empty list to export nothing. # TBD: actually, might work better to export nothing unless declared as such. if exports === nothing @@ -853,11 +927,11 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone if has_comp(obj, comp_name) error("Cannot add two components of the same name ($comp_name)") end - + # check that a time dimension has been set if has_dim(obj, :time) # error("Cannot add component to composite without first setting time dimension.") - + # check that first and last are within the model's time index range time_index = dim_keys(obj, :time) @@ -891,26 +965,26 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone set_param!(obj, comp_name, nameof(param), param.default) end end - + # Return the comp since it's a copy of what was passed in return comp_def end """ - add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, + add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component -is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the +Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component +is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. """ -function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, +function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; exports=nothing, - first::NothingInt=nothing, last::NothingInt=nothing, + first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") - add_comp!(obj, compdef(comp_id), comp_name, + add_comp!(obj, compdef(comp_id), comp_name, exports=exports, first=first, last=last, before=before, after=after) end @@ -920,14 +994,14 @@ end before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) -Replace the component with name `comp_name` in composite component definition `obj` with the -component `comp_id` using the same name. The component is added in the same position as the -old component, unless one of the keywords `before` or `after` is specified. The component is +Replace the component with name `comp_name` in composite component definition `obj` with the +component `comp_id` using the same name. The component is added in the same position as the +old component, unless one of the keywords `before` or `after` is specified. The component is added with the same first and last values, unless the keywords `first` or `last` are specified. -Optional boolean argument `reconnect` with default value `true` indicates whether the existing +Optional boolean argument `reconnect` with default value `true` indicates whether the existing parameter connections should be maintained in the new component. Returns the added comp def. """ -function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, +function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing, @@ -943,13 +1017,13 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, n = length(comps) if n > 1 idx = findfirst(isequal(comp_name), comps) - if idx == n + if idx == n after = comps[idx - 1] else before = comps[idx + 1] end end - end + end # Get original first and last if new run period not specified old_comp = compdef(obj, comp_name) @@ -972,17 +1046,17 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, if !_compare_datum(new_params, old_params) error("Cannot replace and reconnect; new component does not contain the necessary parameters.") end - + # Check outgoing variables _get_name(obj, name) = nameof(compdef(obj, :first)) - outgoing_vars = map(ipc -> ipc.src_var_name, + outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> nameof(compdef(obj, ipc.src_comp_path)) == comp_name, internal_param_conns(obj))) old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) new_vars = new_comp.variables if !_compare_datum(new_vars, old_vars) error("Cannot replace and reconnect; new component does not contain the necessary variables.") end - + # Check external parameter connections remove = [] for epc in external_param_conns(obj, comp_name) @@ -1003,69 +1077,13 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end filter!(epc -> !(epc in remove), external_param_conns(obj)) - # Delete the old component from comps_dict, leaving the existing parameter connections - delete!(obj.comps_dict, comp_name) + # Delete the old component from comps_dict, leaving the existing parameter connections + delete!(obj.comps_dict, comp_name) else # Delete the old component and all its internal and external parameter connections - delete!(obj, comp_name) + delete!(obj, comp_name) end # Re-add return add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) end - -function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) - @info "find_comp($(obj.name), $path; relative=$relative)" - @info "obj.parent = $(printable(obj.parent))" - if (relative || obj.parent === nothing) - if isempty(path) - return obj - end - - names = path.names - if has_comp(obj, names[1]) - return find_comp(compdef(obj, names[1]), ComponentPath(names[2:end])) - end - else - return find_comp(obj, rel_path(obj.comp_path, path)) # perform a relative search - end - return nothing -end - -find_comp(obj::AbstractComponentDef, name::Symbol; relative=true) = find_comp(obj, ComponentPath(name), relative=relative) - -find_comp(obj::ComponentDef, path::ComponentPath; relative=true) = (isempty(path) ? obj : nothing) - -find_comp(obj::AbstractCompositeComponentDef, path::AbstractString; relative=true) = find_comp(obj, comp_path(obj, path), relative=relative) - -""" -Return the relative path of `descendant` if is within the path of composite `ancestor` or -or nothing otherwise. -""" -function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) - a_names = ancestor_path.names - d_names = descendant_path.names - - if ((a_len = length(a_names)) >= (d_len = length(d_names)) || d_names[1:a_len] != a_names) - return nothing - end - - return ComponentPath(d_names[a_len+1:end]) -end - -""" -Return whether component `descendant` is within the composite structure of `ancestor` or -any of its descendants. If the comp_paths check out, the node is located within the -structure to ensure that the component is really where it says it is. (Trust but verify!) -""" -function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) - a_path = ancestor.comp_path - d_path = descendant.comp_path - if (relpath = rel_path(a_path, d_path)) === nothing - return false - end - - return find_comp(ancestor, relpath) -end - - diff --git a/src/core/model.jl b/src/core/model.jl index f25f923a8..a21fe6095 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -33,26 +33,26 @@ is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate add_connector_comps(m::Model) => md """ - connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, src_comp_path::ComponentPath, + connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, src_comp_path::ComponentPath, src_var_name::Symbol, backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) Bind the parameter `dst_par_name` of one component `dst_comp_path` of model `md` to a variable `src_var_name` in another component `src_comp_path` of the same model using `backup` to provide default values and the `ignoreunits` flag to indicate the need to check match units between the two. The `offset` argument indicates the offset -between the destination and the source ie. the value would be `1` if the destination +between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -@delegate connect_param!(m::Model, - dst_comp_path::ComponentPath, dst_par_name::Symbol, - src_comp_path::ComponentPath, src_var_name::Symbol, - backup::Union{Nothing, Array}=nothing; +@delegate connect_param!(m::Model, + dst_comp_path::ComponentPath, dst_par_name::Symbol, + src_comp_path::ComponentPath, src_var_name::Symbol, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) => md -@delegate connect_param!(m::Model, - dst_comp_name::Symbol, dst_par_name::Symbol, +@delegate connect_param!(m::Model, + dst_comp_name::Symbol, dst_par_name::Symbol, src_comp_name::Symbol, src_var_name::Symbol, - backup::Union{Nothing, Array}=nothing; + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) => md """ @@ -62,12 +62,12 @@ Bind the parameter `dst[2]` of one component `dst[1]` of model `md` to a variable `src[2]` in another component `src[1]` of the same model using `backup` to provide default values and the `ignoreunits` flag to indicate the need to check match units between the two. The `offset` argument indicates the offset -between the destination and the source ie. the value would be `1` if the destination +between the destination and the source ie. the value would be `1` if the destination component parameter should only be calculated for the second timestep and beyond. """ -function connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, - backup::Union{Nothing, Array}=nothing; +function connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) connect_param!(m.md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end @@ -77,10 +77,10 @@ end @delegate set_external_param!(m::Model, name::Symbol, value::ModelParameter) => md -@delegate set_external_param!(m::Model, name::Symbol, value::Number; +@delegate set_external_param!(m::Model, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing) => md -@delegate set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; +@delegate set_external_param!(m::Model, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple}; param_dims::Union{Nothing,Array{Symbol}} = nothing) => md @delegate add_internal_param_conn!(m::Model, conn::InternalParameterConnection) => md @@ -93,9 +93,9 @@ end """ update_param!(m::Model, name::Symbol, value; update_timesteps = false) -Update the `value` of an external model parameter in model `m`, referenced by -`name`. Optional boolean argument `update_timesteps` with default value `false` -indicates whether to update the time keys associated with the parameter values +Update the `value` of an external model parameter in model `m`, referenced by +`name`. Optional boolean argument `update_timesteps` with default value `false` +indicates whether to update the time keys associated with the parameter values to match the model's time index. """ @delegate update_param!(m::Model, name::Symbol, value; update_timesteps = false) => md @@ -103,10 +103,10 @@ to match the model's time index. """ update_params!(m::Model, parameters::Dict{T, Any}; update_timesteps = false) where T -For each (k, v) in the provided `parameters` dictionary, `update_param!`` -is called to update the external parameter by name k to value v, with optional +For each (k, v) in the provided `parameters` dictionary, `update_param!`` +is called to update the external parameter by name k to value v, with optional Boolean argument update_timesteps. Each key k must be a symbol or convert to a -symbol matching the name of an external parameter that already exists in the +symbol matching the name of an external parameter that already exists in the model definition. """ @delegate update_params!(m::Model, parameters::Dict; update_timesteps = false) => md @@ -115,7 +115,7 @@ model definition. add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_id` to the model indicated by `m`. The component is added at the end of +Add the component indicated by `comp_id` to the model indicated by `m`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the new name. """ @@ -124,7 +124,7 @@ function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.com return ComponentReference(m.md, comp_name) end -function add_comp!(m::Model, comp_def::ComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; kwargs...) +function add_comp!(m::Model, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; kwargs...) return add_comp!(m, comp_def.comp_id, comp_name; kwargs...) end @@ -133,14 +133,14 @@ end first::NothingSymbol=nothing, last::NothingSymbol=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) - + Replace the component with name `comp_name` in model `m` with the component -`comp_id` using the same name. The component is added in the same position as +`comp_id` using the same name. The component is added in the same position as the old component, unless one of the keywords `before` or `after` is specified. -The component is added with the same first and last values, unless the keywords -`first` or `last` are specified. Optional boolean argument `reconnect` with -default value `true` indicates whether the existing parameter connections -should be maintained in the new component. +The component is added with the same first and last values, unless the keywords +`first` or `last` are specified. Optional boolean argument `reconnect` with +default value `true` indicates whether the existing parameter connections +should be maintained in the new component. """ function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; kwargs...) comp_def = replace_comp!(m.md, comp_id, comp_name; kwargs...) @@ -206,7 +206,7 @@ dim_names(m::Model, comp_name::Symbol, datum_name::Symbol) = dim_names(compdef(m """ dim_count(m::Model, dim_name::Symbol) - + Return the size of index `dim_name` in model `m`. """ @delegate dim_count(m::Model, dim_name::Symbol) => md @@ -215,25 +215,25 @@ Return the size of index `dim_name` in model `m`. """ dim_keys(m::Model, dim_name::Symbol) - + Return keys for dimension `dim-name` in model `m`. """ @delegate dim_keys(m::Model, dim_name::Symbol) => md """ dim_key_dict(m::Model) - + Return a dict of dimension keys for all dimensions in model `m`. """ @delegate dim_key_dict(m::Model) => md """ dim_values(m::Model, name::Symbol) - + Return values for dimension `name` in Model `m`. """ @delegate dim_values(m::Model, name::Symbol) => md """ dim_value_dict(m::Model) - + Return a dictionary of the values of all dimensions in Model `m`. """ @delegate dim_value_dict(m::Model) => md @@ -284,7 +284,7 @@ variables(m::Model, comp_name::Symbol) = variables(compdef(m, comp_name)) """ set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) -Add a one or two dimensional (optionally, time-indexed) array parameter `name` +Add a one or two dimensional (optionally, time-indexed) array parameter `name` with value `value` to the model `m`. """ @delegate set_external_array_param!(m::Model, name::Symbol, value::Union{AbstractArray, TimestepArray}, dims) => md @@ -306,9 +306,9 @@ Delete a `component`` by name from a model `m`'s ModelDef, and nullify the Model """ set_param!(m::Model, comp_name::Symbol, name::Symbol, value, dims=nothing) -Set the parameter of a component `comp_name` in a model `m` to a given `value`. -The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' -is a list of the dimension names of the provided data, and will be used to check +Set the parameter of a component `comp_name` in a model `m` to a given `value`. +The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' +is a list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ @delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, value, dims=nothing) => md @@ -317,7 +317,7 @@ that they match the model's index labels. set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) Set a parameter for a component with the given relative path (as a string), in which "/x" means the -component with name `:x` beneath `m.md`. If the path does not begin with "/", it is treated as +component with name `:x` beneath `m.md`. If the path does not begin with "/", it is treated as relative to `m.md`, which at the top of the hierarchy, produces the same result as starting with "/". """ @delegate set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) => md diff --git a/src/core/types.jl b/src/core/types.jl index ba8843170..bfe55a1f9 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -35,7 +35,7 @@ ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) -ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path1.names...)) +ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path2.names...)) ComponentPath(name::Symbol) = ComponentPath((name,)) @@ -554,7 +554,7 @@ last_period(obj::AbstractComponentInstance) = obj.last # Include only exported vars and pars # """ -Rerun the ComponentInstanceParameters/Variables exported by the given list of +Return the ComponentInstanceParameters/Variables exported by the given list of component instances. """ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, @@ -567,7 +567,7 @@ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, comps_dict = Dict([comp.comp_name => comp for comp in comps]) for (export_name, dr) in comp_def.exports - datum_comp = compdef(dr) + datum_comp = find_comp(dr) datum_name = nameof(dr) ci = comps_dict[nameof(datum_comp)] @@ -595,49 +595,6 @@ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, return vars, pars end -""" -Create a single ComponentInstanceParameters type reflecting those of a composite -component's parameters, and similarly for its variables. -""" -function _comp_instance_vars_pars_OLD(comp_def::AbstractComponentDef, comps::Vector{<: AbstractComponentInstance}) - root = get_root(comp_def) # to find comp_defs by path - exports = comp_def.exports - # @info "Exported by $(comp_def.comp_id): $exports" - - vtypes = DataType[] - vnames = Symbol[] - vvalues = [] - vpaths = [] - - ptypes = DataType[] - pnames = Symbol[] - pvalues = [] - ppaths = [] - - for comp in comps - v = comp.variables - p = comp.parameters - - append!(vnames, names(v)) - append!(pnames, names(p)) - - append!(vtypes, types(v)) - append!(ptypes, types(p)) - - append!(vvalues, values(v)) - append!(pvalues, values(p)) - - append!(vpaths, comp_paths(v)) - append!(ppaths, comp_paths(p)) - end - - vars = ComponentInstanceVariables(vnames, vtypes, vvalues, vpaths) - pars = ComponentInstanceParameters(pnames, ptypes, pvalues, ppaths) - - return vars, pars -end - - @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} diff --git a/test/test_composite.jl b/test/test_composite.jl index b2609ad88..55c7b9b3e 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,8 +4,8 @@ using Test using Mimi import Mimi: - ComponentId, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, - Binding, ExportsDict, ModelDef, build, time_labels, compdef + ComponentId, ComponentPath, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, + Binding, ExportsDict, ModelDef, build, time_labels, compdef, find_comp @defcomp Comp1 begin @@ -24,7 +24,7 @@ end par_2_2 = Parameter(index=[time]) # external input var_2_1 = Variable(index=[time]) # computed foo = Parameter() - + function run_timestep(p, v, d, t) # @info "Comp2 run_timestep" v.var_2_1[t] = p.par_2_1[t] + p.par_2_2[t] @@ -34,7 +34,7 @@ end @defcomp Comp3 begin par_3_1 = Parameter(index=[time]) # connected to Comp2.var_2_1 var_3_1 = Variable(index=[time]) # external output - foo = Parameter() + foo = Parameter(default=30) function run_timestep(p, v, d, t) # @info "Comp3 run_timestep" @@ -42,64 +42,31 @@ end end end -global m = Model() +m = Model() set_dimension!(m, :time, 2005:2020) -md = m.md - -LONG_WAY = false -if LONG_WAY - - # Test the calls the macro will produce the following - comps = [ - (Comp1, [:foo => :foo1]), - (Comp2, [:foo => :foo2]), - (Comp3, [:foo => :foo3]) - ] - - # TBD: need to implement this to create connections and default value - bindings = Binding[] - # DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 - # DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - # DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2) - # ] +@defcomposite top begin + component(Comp1; exports=[foo => foo1]) + component(Comp2, exports=[foo => foo2]) + component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) + # exports(list of names or pairs to export) +end - exports = [] - # DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - # DatumReference(:par_2_2, Comp2) => :c2p2, - # DatumReference(:var_3_1, Comp3) => :c3v1 - # ] +top_ref = add_comp!(m, top, nameof(top)) +top_comp = compdef(top_ref) - compos_name = :top - compos_id = ComponentId(:TestComposite, compos_name) - compos = CompositeComponentDef(compos_id) +md = m.md - top = add_comp!(md, compos, nameof(compos)) # add top-level composite under model def to test 2-layer model +@test find_comp(md, :top) == top_comp - # Add components to composite - for (c, exports) in comps - add_comp!(top, c, nameof(c), exports=exports) # later allow pair for renaming - end -else - exports = [] # TBD: what to export from the composite - bindings = Binding[] - - @defcomposite top begin - component(Comp1; exports=[foo => foo1]) - component(Comp2, exports=[foo => foo2]) - component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) - # exports(list of names or pairs to export) - end - - add_comp!(md, top, nameof(top)) -end +c1 = find_comp(md, ComponentPath((:top, :Comp1)), relative=true) +@test c1.comp_id == Comp1.comp_id -merge!(md.exports, ExportsDict(exports)) -append!(md.bindings, bindings) +c3 = find_comp(md, "/top/Comp3") +@test c3.comp_id == Comp3.comp_id set_param!(m, "/top/Comp1", :foo, 10) set_param!(m, "/top/Comp2", :foo, 20) -set_param!(m, "/top/Comp3", :foo, 30) set_param!(m, "/top/Comp1", :par_1_1, zeros(length(time_labels(md)))) @@ -112,9 +79,4 @@ connect_param!(top, :Comp3, :par_3_1, :Comp2, :var_2_1) end # module -# TBD: remove once debugged -m = TestComposite.m -md = m.md -mi = m.mi - nothing From 1f0b6e561c51b0c43f4cc244ee6975c56c303e6d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 1 Apr 2019 14:23:55 -0700 Subject: [PATCH 46/81] WIP - debugging composites --- src/Mimi.jl | 15 +++-- src/core/connections.jl | 7 +- src/core/defs.jl | 132 ++++++++++---------------------------- src/core/paths.jl | 137 ++++++++++++++++++++++++++++++++++++++++ src/core/types.jl | 32 +++++----- test/test_composite.jl | 2 +- 6 files changed, 203 insertions(+), 122 deletions(-) create mode 100644 src/core/paths.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index fd941a944..4cc3ba8b8 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -68,6 +68,7 @@ include("core/instances.jl") include("core/references.jl") include("core/time.jl") include("core/model.jl") +include("core/paths.jl") include("core/show.jl") # For debugging composites we don't need these @@ -79,6 +80,10 @@ include("utils/lint_helper.jl") include("utils/misc.jl") include("utils/plotting.jl") +# Load built-in components +include("components/adder.jl") +include("components/connector.jl") + """ load_comps(dirname::String="./components") @@ -96,10 +101,10 @@ function load_comps(dirname::String="./components") end end -# Components are defined here to allow pre-compilation to work -function __init__() - compdir = joinpath(@__DIR__, "components") - load_comps(compdir) -end +# # Components are defined here to allow pre-compilation to work +# function __init__() +# compdir = joinpath(@__DIR__, "components") +# load_comps(compdir) +# end end # module diff --git a/src/core/connections.jl b/src/core/connections.jl index 5411006ca..9e791e742 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -25,7 +25,10 @@ Remove any parameter connections for a given parameter `param_name` in a given c `comp_def` which must be a direct subcomponent of composite `obj`. """ function disconnect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) - disconnect_param!(obj, compdef(obj, comp_name), param_name) + # @info "disconnect_param! calling compdef($(printable(obj.comp_id)), $comp_name) (comp_path: $(printable(obj.comp_path)))" + comp = compdef(obj, comp_name) + comp === nothing && error("Did not find $comp_name in composite $(printable(obj.comp_path))") + disconnect_param!(obj, comp, param_name) end """ @@ -35,7 +38,7 @@ Remove any parameter connections for a given parameter `param_name` in the compo `comp_path` which must be under the composite `obj`. """ function disconnect_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol) - if (comp_def = find_comp(obj, comp_path, relative=false)) === nothing + if (comp_def = find_comp(obj, comp_path)) === nothing return end disconnect_param!(obj, comp_def, param_name) diff --git a/src/core/defs.jl b/src/core/defs.jl index fe7a2cab4..823aa4e47 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,13 +1,15 @@ compdef(comp_id::ComponentId) = getfield(getfield(Main, comp_id.module_name), comp_id.comp_name) -compdef(cr::ComponentReference) = find_comp(cr.parent, cr.comp_path; relative=false) +compdef(cr::ComponentReference) = find_comp(cr) + +compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) -compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path; relative=false) compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.comps_dict[comp_name] +has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) + compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) compkeys(c::AbstractCompositeComponentDef) = keys(c.comps_dict) -has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) # Allows method to be called on leaf component defs, which sometimes simplifies code. compdefs(c::ComponentDef) = [] @@ -31,94 +33,13 @@ function reset_compdefs(reload_builtins=true) end end -function comp_path!(parent::AbstractCompositeComponentDef, child::AbstractComponentDef) - child.comp_path = ComponentPath(parent.comp_path, child.name) -end - """ - _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + is_detached(obj::AbstractComponentDef) -Convert a string describing a path from a node to a ComponentPath. The validity -of the path is not checked. If `path` starts with "/", the first element in the -returned component path is set to the root of the hierarchy containing `node`. +Return true if `obj` is not a ModelDef and it has no parent. """ -function _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) - # empty path means just select the node's path - isempty(path) && return node.comp_path - - elts = split(path, "/") - - if elts[1] == "" - root = get_root(node) - elts[1] = String(nameof(root)) - end - return ComponentPath([Symbol(elt) for elt in elts]) -end - -function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath; relative=true) - # @info "find_comp($(obj.name), $path; relative=$relative)" - # @info "obj.parent = $(printable(obj.parent))" - - # Convert "absolute" path from a root node to relative - if ! relative - relative = true - path = rel_path(obj.comp_path, path) - end - - if isempty(path) - return obj - end - - names = path.names - if has_comp(obj, names[1]) - return find_comp(compdef(obj, names[1]), ComponentPath(names[2:end])) - end - - return nothing -end - -find_comp(obj::AbstractComponentDef, name::Symbol) = compdef(obj, name) - -find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) - -function find_comp(obj::AbstractCompositeComponentDef, pathstr::AbstractString) - path = _comp_path(obj, pathstr) - relative = (isempty(pathstr) || pathstr[1] != '/') - find_comp(obj, path, relative=relative) -end - -find_comp(dr::AbstractDatumReference; relative=false) = find_comp(dr.root, dr.comp_path; relative=relative) - -""" -Return the relative path of `descendant` if is within the path of composite `ancestor` or -or nothing otherwise. -""" -function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) - a_names = ancestor_path.names - d_names = descendant_path.names - - if ((a_len = length(a_names)) >= (d_len = length(d_names)) || d_names[1:a_len] != a_names) - # @info "rel_path($a_names, $d_names) returning nothing" - return nothing - end - - return ComponentPath(d_names[a_len+1:end]) -end - -""" -Return whether component `descendant` is within the composite structure of `ancestor` or -any of its descendants. If the comp_paths check out, the node is located within the -structure to ensure that the component is really where it says it is. (Trust but verify!) -""" -function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) - a_path = ancestor.comp_path - d_path = descendant.comp_path - if (relpath = rel_path(a_path, d_path)) === nothing - return false - end - - return find_comp(ancestor, relpath; relative=true) -end +is_detached(obj::AbstractComponentDef) = (obj.parent === nothing) +is_detached(obj::ModelDef) = false # by definition dirty(md::ModelDef) = md.dirty @@ -135,10 +56,7 @@ end dirty!(md::ModelDef) = (md.dirty = true) -function Base.parent(obj::AbstractComponentDef) - parent_path = parent(obj.comp_path) - return compdef(parent_path) -end +Base.parent(obj::AbstractComponentDef) = obj.parent first_period(comp_def::ComponentDef) = comp_def.first last_period(comp_def::ComponentDef) = comp_def.last @@ -178,7 +96,7 @@ function number_type(obj::AbstractCompositeComponentDef) root = get_root(obj) # TBD: hack alert. Need to allow number_type to be specified # for composites that are not yet connected to a ModelDef? - return root isa ModelDef ? number_type(md) : Float64 + return root isa ModelDef ? root.number_type : Float64 end # TBD: should be numcomps() @@ -459,7 +377,11 @@ function parameters(ccd::AbstractCompositeComponentDef) # return cached parameters, if any if length(pars) == 0 for (name, dr) in ccd.exports - cd = find_comp(dr; relative=true) + cd = find_comp(dr) + + if cd === nothing + @info "find_comp failed on path: $(printable(dr.comp_path)), name: $(printable(dr.name)), root: $(printable(dr.root.comp_id))" + end if has_parameter(cd, nameof(dr)) pars[name] = parameter(cd, nameof(dr)) @@ -535,7 +457,8 @@ end function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) - comp = find_comp(obj, comp_path, relative=false) + @info "set_param!($(obj.comp_id), $comp_path, $param_name, $value)" + comp = find_comp(obj, comp_path) @or(comp, error("Component with path $comp_path not found")) set_param!(comp.parent, nameof(comp), param_name, value, dims) end @@ -548,6 +471,7 @@ component with name `:x` beneath the root of the hierarchy in which `obj` is fou not begin with "/", it is treated as relative to `obj`. """ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) + @info "set_param!($(obj.comp_id), $path, $param_name, $value)" set_param!(obj, _comp_path(obj, path), param_name, value, dims) end @@ -560,6 +484,7 @@ list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) + @info "set_param!($(obj.comp_id), $comp_name, $param_name, $value)" # perform possible dimension and labels checks if value isa NamedArray dims = dimnames(value) @@ -569,10 +494,10 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param check_parameter_dimensions(obj, value, dims, param_name) end - comp_param_dims = parameter_dimensions(obj, comp_name, param_name) + comp_def = compdef(obj, comp_name) + comp_param_dims = parameter_dimensions(comp_def, param_name) num_dims = length(comp_param_dims) - comp_def = compdef(obj, comp_name) param = parameter(comp_def, param_name) data_type = param.datatype dtype = data_type == Number ? number_type(obj) : data_type @@ -622,6 +547,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param end # connect_param! calls dirty! so we don't have to + # @info "Calling connect_param!($(printable(obj === nothing ? nothing : obj.comp_id))" connect_param!(obj, comp_name, param_name, param_name) nothing end @@ -673,7 +599,7 @@ variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), va variable(obj::AbstractCompositeComponentDef, comp_name::Symbol, var_name::Symbol) = variable(compdef(obj, comp_name), var_name) function variable(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) - comp_def = find_comp(obj, comp_path, relative=false) + comp_def = find_comp(obj, comp_path) return variable(comp_def, var_name) end @@ -827,7 +753,9 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom comps_dict!(obj, new_comps) end - comp_path!(obj, comp_def) + comp_path!(comp_def, obj) + @info "parent obj comp_path: $(printable(obj.comp_path))" + @info "inserted comp's path: $(comp_def.comp_path)" dirty!(obj) nothing @@ -959,9 +887,15 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone _add_anonymous_dims!(obj, comp_def) _insert_comp!(obj, comp_def, before=before, after=after) + ######################################################################## + # TBD: set parameter values only in ComponentDefs, not in Composites + ######################################################################## + # Set parameters to any specified defaults for param in parameters(comp_def) if param.default !== nothing + x = printable(obj === nothing ? "obj==nothing" : obj.comp_id) + @info "add_comp! calling set_param!($x, $comp_name, $(nameof(param)), $(param.default))" set_param!(obj, comp_name, nameof(param), param.default) end end diff --git a/src/core/paths.jl b/src/core/paths.jl new file mode 100644 index 000000000..3b5b39891 --- /dev/null +++ b/src/core/paths.jl @@ -0,0 +1,137 @@ +# +# ComponentPath manipulation methods +# + +Base.length(path::ComponentPath) = length(path.names) +Base.isempty(path::ComponentPath) = isempty(path.names) + +head(path::ComponentPath) = (isempty(path) ? Symbol[] : path.names[1]) +tail(path::ComponentPath) = ComponentPath(length(path) < 2 ? Symbol[] : path.names[2:end]) + +# The equivalent of ".." in the file system. +Base.parent(path::ComponentPath) = ComponentPath(path.names[1:end-1]) + +# Return a string like "/##ModelDef#367/top/Comp1" +function Base.string(path::ComponentPath) + s = join(path.names, "/") + return is_abspath(path) ? string("/", s) : s +end + +function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) + child.comp_path = ComponentPath(parent.comp_path, child.name) + + # recursively reset all comp_paths + for cd in compdefs(child) + comp_path!(cd, child) + end +end + +""" + _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + +Convert a string describing a path from a node to a ComponentPath. The validity +of the path is not checked. If `path` starts with "/", the first element in the +returned component path is set to the root of the hierarchy containing `node`. +""" +function _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + # empty path means just select the node's path + isempty(path) && return node.comp_path + + elts = split(path, "/") + + if elts[1] == "" # if path starts with "/", elt[1] == "" + root = get_root(node) + elts[1] = string(nameof(root)) + end + return ComponentPath([Symbol(elt) for elt in elts]) +end + +find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) + +function find_comp(obj::AbstractComponentDef, name::Symbol) + # N.B. test here since compdef doesn't check existence + return has_comp(obj, name) ? compdef(obj, name) : nothing +end + + +function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) + # @info "find_comp($(obj.name), $path)" + + # @info "obj.parent = $(printable(obj.parent))" + + if isempty(path) + return obj + end + + # Convert "absolute" path from a root node to relative + if is_abspath(path) + path = rel_path(obj.comp_path, path) + + elseif (child = find_comp(obj, head(path))) !== nothing + path = rel_path(obj.comp_path, child.comp_path) + + elseif nameof(obj) == head(path) + # @info "nameof(obj) == head(path); path: $path" + path = tail(path) + else + error("Cannot find path $path from component $(obj.comp_id)") + end + + names = path.names + if has_comp(obj, names[1]) + return find_comp(compdef(obj, names[1]), ComponentPath(names[2:end])) + end + + return nothing +end + +function find_comp(obj::AbstractCompositeComponentDef, pathstr::AbstractString) + path = _comp_path(obj, pathstr) + find_comp(obj, path) +end + +find_comp(dr::AbstractDatumReference) = find_comp(dr.root, dr.comp_path) + +find_comp(cr::ComponentReference) = find_comp(cr.parent, cr.comp_path) + +""" +Return the relative path of `descendant` if is within the path of composite `ancestor` or +or nothing otherwise. +""" +function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) + a_names = ancestor_path.names + d_names = descendant_path.names + + if ((a_len = length(a_names)) >= (d_len = length(d_names)) || d_names[1:a_len] != a_names) + # @info "rel_path($a_names, $d_names) returning nothing" + return nothing + end + + return ComponentPath(d_names[a_len+1:end]) +end + +""" +Return whether component `descendant` is within the composite structure of `ancestor` or +any of its descendants. If the comp_paths check out, the node is located within the +structure to ensure that the component is really where it says it is. (Trust but verify!) +""" +function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) + a_path = ancestor.comp_path + d_path = descendant.comp_path + if (relpath = rel_path(a_path, d_path)) === nothing + return false + end + + # @info "is_descendant calling find_comp($a_path, $relpath)" + return find_comp(ancestor, relpath) +end + +""" + is_abspath(path::ComponentPath) + +Return true if the path starts from a ModelDef, whose name is generated with +gensym("ModelDef") names look like Symbol("##ModelDef#123") +""" +function is_abspath(path::ComponentPath) + return ! isempty(path) && match(r"^##ModelDef#\d+$", string(path.names[1])) !== nothing +end \ No newline at end of file diff --git a/src/core/types.jl b/src/core/types.jl index bfe55a1f9..b60a0f01d 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -23,30 +23,23 @@ struct ComponentId <: MimiStruct comp_name::Symbol end -# Identifies the path through multiple composites to a leaf component -# TBD: Could be just a tuple of Symbols since they are unique at each level. +ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) + +# ComponentPath identifies the path through multiple composites to a leaf comp. struct ComponentPath <: MimiStruct names::NTuple{N, Symbol} where N end -const ParamPath = Tuple{ComponentPath, Symbol} - ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) +ComponentPath(names::Vararg{Symbol}) = ComponentPath(names) -ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath((path.names..., name)) +ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath(path.names..., name) -ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath((path1.names..., path2.names...)) - -ComponentPath(name::Symbol) = ComponentPath((name,)) +ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath(path1.names..., path2.names...) ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) -Base.isempty(obj::ComponentPath) = isempty(obj.names) - -# The equivalent of ".." in the file system. -Base.parent(path::ComponentPath) = ComponentPath(path.names[1:end-1]) - -ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) +const ParamPath = Tuple{ComponentPath, Symbol} # # 1. Types supporting parameterized Timestep and Clock objects @@ -350,6 +343,7 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::Union{Nothing, ComponentId}=nothing) ComponentDef(self, comp_id) # call superclass' initializer + self.comp_path = ComponentPath(self.name) self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() self.bindings = Vector{Binding}() self.exports = ExportsDict() @@ -370,6 +364,11 @@ function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Ve for c in subcomps subcomp_id = ComponentId(@or(c.module_name, calling_module), c.comp_name) subcomp = compdef(subcomp_id) + + x = printable(subcomp === nothing ? nothing : subcomp_id) + y = printable(composite === nothing ? nothing : comp_id) + @info "CompositeComponentDef calling add_comp!($y, $x)" + add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) end return composite @@ -400,7 +399,10 @@ ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath( function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - self.comp_path = ComponentPath(self.name) + + # TBD: now set in CompositeComponentDef(self); delete if that works better + # self.comp_path = ComponentPath(self.name) + return ModelDef(self, number_type, false) # call @class-generated method end end diff --git a/test/test_composite.jl b/test/test_composite.jl index 55c7b9b3e..86ede80f3 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -59,7 +59,7 @@ md = m.md @test find_comp(md, :top) == top_comp -c1 = find_comp(md, ComponentPath((:top, :Comp1)), relative=true) +c1 = find_comp(md, ComponentPath(:top, :Comp1)) @test c1.comp_id == Comp1.comp_id c3 = find_comp(md, "/top/Comp3") From b6b4d885c2703cfa1775ff5bd5f12db3a1f3675e Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 1 Apr 2019 15:14:00 -0700 Subject: [PATCH 47/81] WIP - all test passing (except test_dimensions, still excluded.) --- src/core/defs.jl | 28 +++++++++++++--------------- src/core/paths.jl | 9 +++++---- src/core/types.jl | 2 +- test/test_model_structure.jl | 2 +- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 823aa4e47..1156f861e 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -457,7 +457,7 @@ end function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) - @info "set_param!($(obj.comp_id), $comp_path, $param_name, $value)" + # @info "set_param!($(obj.comp_id), $comp_path, $param_name, $value)" comp = find_comp(obj, comp_path) @or(comp, error("Component with path $comp_path not found")) set_param!(comp.parent, nameof(comp), param_name, value, dims) @@ -471,7 +471,7 @@ component with name `:x` beneath the root of the hierarchy in which `obj` is fou not begin with "/", it is treated as relative to `obj`. """ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) - @info "set_param!($(obj.comp_id), $path, $param_name, $value)" + # @info "set_param!($(obj.comp_id), $path, $param_name, $value)" set_param!(obj, _comp_path(obj, path), param_name, value, dims) end @@ -484,7 +484,7 @@ list of the dimension names of the provided data, and will be used to check that they match the model's index labels. """ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) - @info "set_param!($(obj.comp_id), $comp_name, $param_name, $value)" + # @info "set_param!($(obj.comp_id), $comp_name, $param_name, $value)" # perform possible dimension and labels checks if value isa NamedArray dims = dimnames(value) @@ -754,8 +754,8 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom end comp_path!(comp_def, obj) - @info "parent obj comp_path: $(printable(obj.comp_path))" - @info "inserted comp's path: $(comp_def.comp_path)" + # @info "parent obj comp_path: $(printable(obj.comp_path))" + # @info "inserted comp's path: $(comp_def.comp_path)" dirty!(obj) nothing @@ -887,16 +887,14 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone _add_anonymous_dims!(obj, comp_def) _insert_comp!(obj, comp_def, before=before, after=after) - ######################################################################## - # TBD: set parameter values only in ComponentDefs, not in Composites - ######################################################################## - - # Set parameters to any specified defaults - for param in parameters(comp_def) - if param.default !== nothing - x = printable(obj === nothing ? "obj==nothing" : obj.comp_id) - @info "add_comp! calling set_param!($x, $comp_name, $(nameof(param)), $(param.default))" - set_param!(obj, comp_name, nameof(param), param.default) + # Set parameters to any specified defaults, but only for leaf components + if is_leaf(comp_def) + for param in parameters(comp_def) + if param.default !== nothing + x = printable(obj === nothing ? "obj==nothing" : obj.comp_id) + # @info "add_comp! calling set_param!($x, $comp_name, $(nameof(param)), $(param.default))" + set_param!(obj, comp_name, nameof(param), param.default) + end end end diff --git a/src/core/paths.jl b/src/core/paths.jl index 3b5b39891..08ac70629 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -56,7 +56,6 @@ end function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) # @info "find_comp($(obj.name), $path)" - # @info "obj.parent = $(printable(obj.parent))" if isempty(path) @@ -66,15 +65,17 @@ function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) # Convert "absolute" path from a root node to relative if is_abspath(path) path = rel_path(obj.comp_path, path) + # @info "abspath converted to relpath is $path" elseif (child = find_comp(obj, head(path))) !== nothing - path = rel_path(obj.comp_path, child.comp_path) + # @info "path is unchanged: $path" + elseif nameof(obj) == head(path) - # @info "nameof(obj) == head(path); path: $path" + # @info "nameof(obj) == head(path); path: $(printable(path))" path = tail(path) else - error("Cannot find path $path from component $(obj.comp_id)") + error("Cannot find path $(printable(path)) from component $(printable(obj.comp_id))") end names = path.names diff --git a/src/core/types.jl b/src/core/types.jl index b60a0f01d..0d4b6b93f 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -367,7 +367,7 @@ function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Ve x = printable(subcomp === nothing ? nothing : subcomp_id) y = printable(composite === nothing ? nothing : comp_id) - @info "CompositeComponentDef calling add_comp!($y, $x)" + # @info "CompositeComponentDef calling add_comp!($y, $x)" add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) end diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 9e110eed5..af466621b 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -8,7 +8,7 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, - modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs, comp_path + modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs @defcomp A begin varA::Int = Variable(index=[time]) From 2c480820df3b90e161ddac0236ca2015827dd02e Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 4 Apr 2019 08:28:34 -0700 Subject: [PATCH 48/81] WIP - moved load_comps from Mimi.jl to utils. - simplified first/last period methods - reinstated test_dimensions and fixed tests (after removing warning about time redefinition) --- src/Mimi.jl | 23 ------ src/core/connections.jl | 4 +- src/core/defs.jl | 138 +++++++++++++++++------------------ src/core/model.jl | 2 - src/core/types.jl | 2 + src/utils/getdataframe.jl | 4 +- src/utils/misc.jl | 17 +++++ test/runtests.jl | 5 +- test/test_dimensions.jl | 23 ++---- test/test_metainfo.jl | 2 +- test/test_model_structure.jl | 10 +-- test/test_timesteps.jl | 7 +- 12 files changed, 103 insertions(+), 134 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index 4cc3ba8b8..ecacd65e9 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -84,27 +84,4 @@ include("utils/plotting.jl") include("components/adder.jl") include("components/connector.jl") -""" - load_comps(dirname::String="./components") - -Call include() on all the files in the indicated directory `dirname`. -This avoids having modelers create a long list of include() -statements. Just put all the components in a directory. -""" -function load_comps(dirname::String="./components") - files = readdir(dirname) - for file in files - if endswith(file, ".jl") - pathname = joinpath(dirname, file) - include(pathname) - end - end -end - -# # Components are defined here to allow pre-compilation to work -# function __init__() -# compdir = joinpath(@__DIR__, "components") -# load_comps(compdir) -# end - end # module diff --git a/src/core/connections.jl b/src/core/connections.jl index 9e791e742..ff63bd8f1 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -78,8 +78,8 @@ function _check_labels(obj::AbstractCompositeComponentDef, param_length = size(ext_param.values)[i] if dim == :time t = dimension(obj, :time) - first = first_period(obj, comp_def) - last = last_period(obj, comp_def) + first = find_first_period(comp_def) + last = find_last_period(comp_def) comp_length = t[last] - t[first] + 1 else comp_length = dim_count(obj, dim) diff --git a/src/core/defs.jl b/src/core/defs.jl index 1156f861e..4415c271a 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -56,31 +56,6 @@ end dirty!(md::ModelDef) = (md.dirty = true) -Base.parent(obj::AbstractComponentDef) = obj.parent - -first_period(comp_def::ComponentDef) = comp_def.first -last_period(comp_def::ComponentDef) = comp_def.last - -function first_period(comp::AbstractCompositeComponentDef) - values = filter(!isnothing, [first_period(c) for c in compdefs(comp)]) - return length(values) > 0 ? min(values...) : nothing -end - -function last_period(comp::AbstractCompositeComponentDef) - values = filter(!isnothing, [last_period(c) for c in compdefs(comp)]) - return length(values) > 0 ? max(values...) : nothing -end - -function first_period(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) - period = first_period(comp_def) - return period === nothing ? time_labels(obj)[1] : period -end - -function last_period(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) - period = last_period(comp_def) - return period === nothing ? time_labels(obj)[end] : period -end - compname(dr::AbstractDatumReference) = dr.comp_path.names[end] #@delegate compmodule(dr::DatumReference) => comp_id @@ -99,6 +74,12 @@ function number_type(obj::AbstractCompositeComponentDef) return root isa ModelDef ? root.number_type : Float64 end +first_period(root::AbstractCompositeComponentDef, comp::AbstractComponentDef) = @or(first_period(comp), first_period(root)) +last_period(root::AbstractCompositeComponentDef, comp::AbstractComponentDef) = @or(last_period(comp), last_period(root)) + +find_first_period(comp_def::AbstractComponentDef) = @or(first_period(comp_def), first_period(get_root(comp_def))) +find_last_period(comp_def::AbstractComponentDef) = @or(last_period(comp_def), last_period(get_root(comp_def))) + # TBD: should be numcomps() numcomponents(obj::AbstractComponentDef) = 0 # no sub-components numcomponents(obj::AbstractCompositeComponentDef) = length(obj.comps_dict) @@ -162,33 +143,26 @@ dim_names(comp_def::AbstractComponentDef, datum_name::Symbol) = dim_names(datumd dim_count(def::AbstractDatumDef) = length(dim_names(def)) -function step_size(values::Vector{Int}) - return length(values) > 1 ? values[2] - values[1] : 1 -end +step_size(values::Vector{Int}) = (length(values) > 1 ? values[2] - values[1] : 1) # # TBD: should these be defined as methods of CompositeComponentDef? # -function step_size(obj::AbstractCompositeComponentDef) - keys::Vector{Int} = time_labels(obj) +function step_size(obj::AbstractComponentDef) + keys = time_labels(obj) return step_size(keys) end -function first_and_step(obj::AbstractCompositeComponentDef) - keys::Vector{Int} = time_labels(obj) # labels are the first times of the model runs +function first_and_step(obj::AbstractComponentDef) + keys = time_labels(obj) return first_and_step(keys) end -function first_and_step(values::Vector{Int}) - return values[1], step_size(values) -end +first_and_step(values::Vector{Int}) = (values[1], step_size(values)) first_and_last(obj::AbstractComponentDef) = (obj.first, obj.last) -function time_labels(obj::AbstractCompositeComponentDef) - keys::Vector{Int} = dim_keys(obj, :time) - return keys -end +time_labels(obj::AbstractComponentDef) = dim_keys(obj, :time) function check_parameter_dimensions(md::ModelDef, value::AbstractArray, dims::Vector, name::Symbol) for dim in dims @@ -221,6 +195,12 @@ function datum_size(obj::AbstractCompositeComponentDef, comp_def::ComponentDef, return datum_size end +######## +# Should these all be defined for leaf ComponentDefs? What is the time (or other) dimension for +# a leaf component? Is time always determined from ModelDef? What about other dimensions that may +# be defined differently in a component? +######## + # Symbols are added to the dim_dict in @defcomp (with value of nothing), but are set later using set_dimension! has_dim(obj::AbstractCompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) @@ -243,41 +223,53 @@ dim_count(obj::AbstractCompositeComponentDef, name::Symbol) = length(dimension(o dim_keys(obj::AbstractCompositeComponentDef, name::Symbol) = collect(keys(dimension(obj, name))) dim_values(obj::AbstractCompositeComponentDef, name::Symbol) = collect(values(dimension(obj, name))) -# For debugging only -function _show_run_period(obj::AbstractComponentDef, first, last) - first = (first === nothing ? :nothing : first) - last = (last === nothing ? :nothing : last) - which = (is_leaf(obj) ? :leaf : :composite) - @info "Setting run period for $which $(nameof(obj)) to ($first, $last)" -end +""" + check_run_period(obj::AbstractComponentDef, first, last) +Raise an error if the component has an earlier start than `first` or a later finish than +`last`. Values of `nothing` are not checked. Composites recurse to check sub-components. """ - set_run_period!(obj::ComponentDef, first, last) +function check_run_period(obj::AbstractComponentDef, new_first, new_last) + # @info "check_run_period($(obj.comp_id), $(printable(new_first)), $(printable(new_last))" + old_first = first_period(obj) + old_last = last_period(obj) -Allows user to narrow the bounds on the time dimension. + if new_first !== nothing && old_first !== nothing && new_first < old_first + error("Attempted to set first period of $(obj.comp_id) to an earlier period ($new_first) than component indicates ($old_first)") + end + + if new_last !== nothing && old_last !== nothing && new_last > old_last + error("Attempted to set last period of $(obj.comp_id) to a later period ($new_last) than component indicates ($old_last)") + end + + # N.B. compdefs() returns an empty list for leaf ComponentDefs + for subcomp in compdefs(obj) + check_run_period(subcomp, new_first, new_last) + end + + nothing +end -If the component has an earlier start than `first` or a later finish than `last`, -the values are reset to the tighter bounds. Values of `nothing` are left unchanged. -Composites recurse on sub-components. """ -function set_run_period!(obj::AbstractComponentDef, first, last) - #_show_run_period(obj, first, last) + _set_run_period!(obj::AbstractComponentDef, first, last) + +Allows user to change the bounds on a AbstractComponentDef's time dimension. +An error is raised if the new time bounds are outside those of any +subcomponent, recursively. +""" +function _set_run_period!(obj::AbstractComponentDef, first, last) + check_run_period(obj, first, last) + first_per = first_period(obj) last_per = last_period(obj) changed = false if first !== nothing - if first_per !== nothing && first_per < first - @warn "Resetting $(nameof(comp_def)) component's first timestep to $first" - end obj.first = first changed = true end if last !== nothing - if last_per !== nothing && last_per > last - @warn "Resetting $(nameof(comp_def)) component's last timestep to $last" - end obj.last = last changed = true end @@ -286,11 +278,6 @@ function set_run_period!(obj::AbstractComponentDef, first, last) dirty!(obj) end - # N.B. compdefs() returns an empty list for leaf ComponentDefs - for subcomp in compdefs(obj) - set_run_period!(subcomp, first, last) - end - nothing end @@ -302,13 +289,13 @@ an integer; or to the values in the vector or range if `keys` is either of those """ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) redefined = has_dim(ccd, name) - if redefined - @warn "Redefining dimension :$name" - end + # if redefined + # @warn "Redefining dimension :$name" + # end if name == :time + _set_run_period!(ccd, keys[1], keys[end]) set_uniform!(ccd, isuniform(keys)) - #set_run_period!(ccd, keys[1], keys[end]) end return set_dimension!(ccd, name, Dimension(keys)) @@ -522,7 +509,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param if num_dims == 0 values = value else - # Want to use the first from the comp_def if it has it, if not use ModelDef + # Use the first from the comp_def if it has it, else use the tree root (usu. a ModelDef) first = first_period(obj, comp_def) if isuniform(obj) @@ -668,7 +655,7 @@ end # # Return the number of timesteps a given component in a model will run for. -function getspan(obj::AbstractCompositeComponentDef, comp_name::Symbol) +function getspan(obj::AbstractComponentDef, comp_name::Symbol) comp_def = compdef(obj, comp_name) return getspan(obj, comp_def) end @@ -824,6 +811,11 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) + # When adding composites to another composite, we disallow setting first and last periods. + if is_composite(comp_def) && (first !== nothing || last !== nothing) + error("Cannot set first or last period when adding a composite component: $(comp_def.comp_id)") + end + # If not specified, export all var/pars. Caller can pass empty list to export nothing. # TBD: actually, might work better to export nothing unless declared as such. if exports === nothing @@ -856,12 +848,12 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot add two components of the same name ($comp_name)") end - # check that a time dimension has been set + # check time constraints if the time dimension has been set if has_dim(obj, :time) # error("Cannot add component to composite without first setting time dimension.") # check that first and last are within the model's time index range - time_index = dim_keys(obj, :time) + time_index = time_labels(obj) if first !== nothing && first < time_index[1] error("Cannot add component $comp_name with first time before first of model's time index range.") @@ -883,7 +875,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone comp_def.name = comp_name parent!(comp_def, obj) - set_run_period!(comp_def, first, last) + _set_run_period!(comp_def, first, last) _add_anonymous_dims!(obj, comp_def) _insert_comp!(obj, comp_def, before=before, after=after) diff --git a/src/core/model.jl b/src/core/model.jl index a21fe6095..d5e7ca6e3 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -245,8 +245,6 @@ an integer; or to the values in the vector or range if `keys`` is either of thos """ @delegate set_dimension!(m::Model, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) => md -@delegate set_run_period!(m::Model, first, last) => md - @delegate check_parameter_dimensions(m::Model, value::AbstractArray, dims::Vector, name::Symbol) => md @delegate parameter_names(m::Model, comp_name::Symbol) => md diff --git a/src/core/types.jl b/src/core/types.jl index 0d4b6b93f..da04dae89 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -297,6 +297,8 @@ first_period(obj::AbstractComponentDef) = obj.first last_period(obj::AbstractComponentDef) = obj.last isuniform(obj::AbstractComponentDef) = obj.is_uniform +Base.parent(obj::AbstractComponentDef) = obj.parent + # Stores references to the name of a component variable or parameter # and the ComponentPath of the component in which it is defined @class DatumReference <: NamedObj begin diff --git a/src/utils/getdataframe.jl b/src/utils/getdataframe.jl index 3049791c1..d93e42b72 100644 --- a/src/utils/getdataframe.jl +++ b/src/utils/getdataframe.jl @@ -75,7 +75,7 @@ function _df_helper(m::Model, comp_name::Symbol, item_name::Symbol, dims::Vector df[dim1name] = repeat(keys1, inner = [len_dim2]) df[dim2name] = repeat(keys2, outer = [len_dim1]) - if dim1name == :time && size(data)[1] != len_dim1 #length(time_labels(md)) + if dim1name == :time && size(data)[1] != len_dim1 mi = modelinstance(m) ci = compinstance(mi, comp_name) t = dimension(m, :time) @@ -91,7 +91,7 @@ function _df_helper(m::Model, comp_name::Symbol, item_name::Symbol, dims::Vector else # shift the data to be padded with missings if this data is shorter than the model - if dim1name == :time && size(data)[1] != len_dim1 #length(time_labels(md)) + if dim1name == :time && size(data)[1] != len_dim1 mi = modelinstance(m) ci = compinstance(mi, comp_name) t = dimension(m, :time) diff --git a/src/utils/misc.jl b/src/utils/misc.jl index 3fbaafa5e..1be1d2f6e 100644 --- a/src/utils/misc.jl +++ b/src/utils/misc.jl @@ -57,3 +57,20 @@ function pretty_string(s::String) end pretty_string(s::Symbol) = pretty_string(string(s)) + +""" + load_comps(dirname::String="./components") + +Call include() on all the files in the indicated directory `dirname`. +This avoids having modelers create a long list of include() +statements. Just put all the components in a directory. +""" +function load_comps(dirname::String="./components") + files = readdir(dirname) + for file in files + if endswith(file, ".jl") + pathname = joinpath(dirname, file) + include(pathname) + end + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 3ebdbb53a..75d779fd5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -75,9 +75,8 @@ using Test @info("test_timesteparrays.jl") include("test_timesteparrays.jl") - @warn("SKIPPING test_dimensions") # question about proper behavior in comp first/last when setting :time - # @info("test_dimensions") - # include("test_dimensions.jl") + @info("test_dimensions") + include("test_dimensions.jl") @info("test_datum_storage.jl") include("test_datum_storage.jl") diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index 815afb038..cb47aad7c 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -94,29 +94,20 @@ set_dimension!(m, :time, 2000:2100) foo2_ref = add_comp!(m, foo2; first = 2005, last = 2095) my_foo2 = compdef(foo2_ref) -# Test that foo's time dimension is unchanged -@test_logs( - (:warn, "Redefining dimension :time"), - set_dimension!(m, :time, 1990:2200) -) +# Can't set time more narrowly than components are defined as +@test_throws ErrorException set_dimension!(m, :time, 1990:2200) @test first_period(my_foo2) == 2005 @test last_period(my_foo2) == 2095 # Test parameter connections @test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long + set_param!(m, :foo2, :x, 2005:2095) # Shouldn't throw an error -# Test that foo's time dimension is updated -@test_logs( - (:warn, "Redefining dimension :time"), - # (:warn, "Resetting foo2 component's first timestep to 2010"), - # (:warn, "Resetting foo2 component's last timestep to 2050"), - set_dimension!(m, :time, 2010:2050) -) - -# TBD: should these be changed as a result of changing model time? -@test first_period(my_foo2) == 2010 -@test last_period(my_foo2) == 2050 +set_dimension!(m, :time, 2010:2050) + +@test first_period(m.md) == 2010 +@test last_period(m.md) == 2050 end #module diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 77b8af540..2281d626c 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - compdef, compname, reset_compdefs, compmodule, first_period, last_period, + compdef, compname, reset_compdefs, compmodule, first_period, last_period, variable_names reset_compdefs() diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index af466621b..88b9414fa 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -6,7 +6,7 @@ using Test using Mimi import Mimi: - connect_param!, unconnected_params, set_dimension!, + connect_param!, unconnected_params, set_dimension!, build, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs @@ -133,7 +133,7 @@ delete!(m, :A) end add_comp!(m, D) -@test_throws ErrorException Mimi.build(m) +@test_throws ErrorException build(m) ########################################## # Test init function # @@ -168,11 +168,7 @@ run(m) @test m[:E, :varE] == 10 # run for just one timestep, so init sets the value here -# This results in 2 warnings, so we test for both. -@test_logs( - (:warn, "Redefining dimension :time"), - set_dimension!(m, :time, [2015]) -) +set_dimension!(m, :time, [2015]) run(m) @test m[:E, :varE] == 1 diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index 1bf3088a4..c3ee572ac 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -155,11 +155,8 @@ t_matrix = get_timestep_array(m.md, Float64, 2, matrix) @test typeof(t_matrix) <: TimestepMatrix -#try with variable timestep -@test_logs( - (:warn, "Redefining dimension :time"), - set_dimension!(m, :time, [2000:1:2004; 2005:2:2016]) -) +# try with variable timestep +set_dimension!(m, :time, [2000:1:2004; 2005:2:2009]) t_vector= get_timestep_array(m.md, Float64, 1, vector) t_matrix = get_timestep_array(m.md, Float64, 2, matrix) From ef6edc1bd24c0423be0d8859b815157ec8a7e23d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 8 Apr 2019 10:41:03 -0700 Subject: [PATCH 49/81] Disabled 'first' and 'last' args on add_comp! and replace_comp! --- src/core/defs.jl | 16 ++++++++++++++++ src/core/model.jl | 11 ++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 4415c271a..5ac3363a9 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -804,6 +804,8 @@ The `exports` arg identifies which vars/pars to make visible to the next higher what names. If `nothing`, everything is exported. The first element of a pair indicates the symbol to export from comp_def to the composite, the second element allows this var/par to have a new name in the composite. A symbol alone means to use the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] + +Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; @@ -811,6 +813,11 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) + if first !== nothing || last !== nothing + @warn "add_comp!: Keyword arguments 'first' and 'last' are currently disabled." + first = last = nothing + end + # When adding composites to another composite, we disallow setting first and last periods. if is_composite(comp_def) && (first !== nothing || last !== nothing) error("Cannot set first or last period when adding a composite component: $(comp_def.comp_id)") @@ -901,6 +908,8 @@ end Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. + +Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; @@ -924,6 +933,8 @@ old component, unless one of the keywords `before` or `after` is specified. The added with the same first and last values, unless the keywords `first` or `last` are specified. Optional boolean argument `reconnect` with default value `true` indicates whether the existing parameter connections should be maintained in the new component. Returns the added comp def. + +Note: `first` and `last` keywords are currently disabled. """ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; @@ -931,6 +942,11 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, before::NothingSymbol=nothing, after::NothingSymbol=nothing, reconnect::Bool=true) + if first !== nothing || last !== nothing + @warn "replace_comp!: Keyword arguments 'first' and 'last' are currently disabled." + first = last = nothing + end + if ! has_comp(obj, comp_name) error("Cannot replace '$comp_name'; component not found in model.") end diff --git a/src/core/model.jl b/src/core/model.jl index d5e7ca6e3..e0de76deb 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -115,9 +115,12 @@ model definition. add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_id` to the model indicated by `m`. The component is added at the end of -the list unless one of the keywords, `first`, `last`, `before`, `after`. If the `comp_name` -differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the new name. +Add the component indicated by `comp_id` to the model indicated by `m`. The component is added +at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the +`comp_name` differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the +new name. + +Note: `first` and `last` keywords are currently disabled. """ function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; kwargs...) comp_def = add_comp!(m.md, comp_id, comp_name; kwargs...) @@ -141,6 +144,8 @@ The component is added with the same first and last values, unless the keywords `first` or `last` are specified. Optional boolean argument `reconnect` with default value `true` indicates whether the existing parameter connections should be maintained in the new component. + +Note: `first` and `last` keywords are currently disabled. """ function replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; kwargs...) comp_def = replace_comp!(m.md, comp_id, comp_name; kwargs...) From 59986c7e1bd1e39f7cea4958cb838eaace770ab6 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 30 Apr 2019 10:19:56 -0700 Subject: [PATCH 50/81] Adjusted tests to disabled first and last parameters to add_comp!. --- src/core/connections.jl | 4 +- src/core/defs.jl | 13 ++- test/test_components.jl | 28 +++-- test/test_connectorcomp.jl | 72 +++++++----- test/test_datum_storage.jl | 104 ++++++++++++++---- test/test_dimensions.jl | 28 +++-- test/test_explorer.jl | 2 +- test/test_getdataframe.jl | 77 +++++++++---- test/test_model_structure_variabletimestep.jl | 23 +++- test/test_parametertypes.jl | 71 ++++++------ test/test_plotting.jl | 4 +- test/test_timesteps.jl | 9 +- 12 files changed, 288 insertions(+), 147 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index ff63bd8f1..ea2aae307 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -53,7 +53,7 @@ function _check_labels(obj::AbstractCompositeComponentDef, t1 = eltype(ext_param.values) t2 = eltype(param_def.datatype) - if !(t1 <: t2) + if !(t1 <: Union{Missing, t2}) error("Mismatched datatype of parameter connection: Component: $(comp_def.comp_id) ($t1), Parameter: $param_name ($t2)") end @@ -437,7 +437,7 @@ Add an array type parameter `name` with value `value` and `dims` dimensions to t """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::AbstractArray, dims) - numtype = number_type(obj) + numtype = Union{Missing, number_type(obj)} if !(typeof(value) <: Array{numtype}) # Need to force a conversion (simple convert may alias in v0.6) value = Array{numtype}(undef, value) diff --git a/src/core/defs.jl b/src/core/defs.jl index 5ac3363a9..837dcd905 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -224,13 +224,13 @@ dim_keys(obj::AbstractCompositeComponentDef, name::Symbol) = collect(keys(dime dim_values(obj::AbstractCompositeComponentDef, name::Symbol) = collect(values(dimension(obj, name))) """ - check_run_period(obj::AbstractComponentDef, first, last) + _check_run_period(obj::AbstractComponentDef, first, last) Raise an error if the component has an earlier start than `first` or a later finish than `last`. Values of `nothing` are not checked. Composites recurse to check sub-components. """ -function check_run_period(obj::AbstractComponentDef, new_first, new_last) - # @info "check_run_period($(obj.comp_id), $(printable(new_first)), $(printable(new_last))" +function _check_run_period(obj::AbstractComponentDef, new_first, new_last) + # @info "_check_run_period($(obj.comp_id), $(printable(new_first)), $(printable(new_last))" old_first = first_period(obj) old_last = last_period(obj) @@ -244,7 +244,7 @@ function check_run_period(obj::AbstractComponentDef, new_first, new_last) # N.B. compdefs() returns an empty list for leaf ComponentDefs for subcomp in compdefs(obj) - check_run_period(subcomp, new_first, new_last) + _check_run_period(subcomp, new_first, new_last) end nothing @@ -258,7 +258,8 @@ An error is raised if the new time bounds are outside those of any subcomponent, recursively. """ function _set_run_period!(obj::AbstractComponentDef, first, last) - check_run_period(obj, first, last) + # We've disabled `first` and `last` args to add_comp!, so we don't test bounds + # _check_run_period(obj, first, last) first_per = first_period(obj) last_per = last_period(obj) @@ -487,7 +488,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param param = parameter(comp_def, param_name) data_type = param.datatype - dtype = data_type == Number ? number_type(obj) : data_type + dtype = Union{Missing, (data_type == Number ? number_type(obj) : data_type)} if length(comp_param_dims) > 0 diff --git a/test/test_components.jl b/test/test_components.jl index 99db900b6..564ac2ef7 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -135,10 +135,23 @@ ci = compinstance(m, :C) # Get the component instance m = Model() set_dimension!(m, :time, 2000:2100) -add_comp!(m, testcomp1, :C; first=2010, last=2090) # Give explicit first and last values for the component + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, testcomp1, :C; first=2010, last=2090) # Give explicit first and last values for the component +) + cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first == 2010 # First and last values are defined in the comp def because they were explicitly given -@test cd.last == 2090 + +# first and last are disabled currently +# @test cd.first == 2010 # First and last values are defined in the comp def because they were explicitly given +# @test cd.last == 2090 + +# Verify that they didn't change +@test cd.first === nothing +@test cd.last === nothing + +set_dimension!(m, :time, 2010:2090) set_param!(m, :C, :par1, zeros(81)) Mimi.build(m) # Build the model @@ -148,13 +161,12 @@ ci = compinstance(m, :C) # Get the component instance set_dimension!(m, :time, 2000:2200) # Reset the time dimension cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first == 2010 # First and last values should still be the same -@test cd.last == 2090 +# @test cd.first == 2010 # First and last values should still be the same +# @test cd.last == 2090 Mimi.build(m) # Build the model ci = compinstance(m, :C) # Get the component instance -@test ci.first == 2010 # The component instance's first and last values are the same as the comp def -@test ci.last == 2090 - +# @test ci.first == 2010 # The component instance's first and last values are the same as the comp def +# @test ci.last == 2090 end #module diff --git a/test/test_connectorcomp.jl b/test/test_connectorcomp.jl index 2bf12c712..bc5adbce5 100644 --- a/test/test_connectorcomp.jl +++ b/test/test_connectorcomp.jl @@ -9,17 +9,20 @@ import Mimi: compdef, compdefs x = Parameter(index=[time]) end +late_start = 2005 + @defcomp Short begin a = Parameter() b = Variable(index=[time]) function run_timestep(p, v, d, t) - v.b[t] = p.a * t.t + if gettime(t) >= late_start + v.b[t] = p.a * t.t + end end end years = 2000:2010 -late_start = 2005 year_dim = Mimi.Dimension(years) @@ -30,7 +33,7 @@ year_dim = Mimi.Dimension(years) model1 = Model() set_dimension!(model1, :time, years) -add_comp!(model1, Short; first=late_start) +add_comp!(model1, Short) #; first=late_start) add_comp!(model1, Long) set_param!(model1, :Short, :a, 2.) connect_param!(model1, :Long, :x, :Short, :b, zeros(length(years))) @@ -48,7 +51,9 @@ x = model1[:Long, :x] @test length(x) == length(years) @test all(ismissing, b[1:year_dim[late_start]-1]) -@test all(iszero, x[1:year_dim[late_start]-1]) + +#@test all(iszero, x[1:year_dim[late_start]-1]) +@test all(ismissing, x[1:year_dim[late_start]-1]) # Test the values are right after the late start @test b[year_dim[late_start]:end] == x[year_dim[late_start]:end] @@ -71,7 +76,7 @@ early_last = 2010 model2 = Model() set_dimension!(model2, :time, years_variable) -add_comp!(model2, Short; last=early_last) +add_comp!(model2, Short) #; last=early_last) add_comp!(model2, Long) set_param!(model2, :Short, :a, 2.) connect_param!(model2, :Long, :x, :Short, :b, zeros(length(years_variable))) @@ -88,13 +93,16 @@ x = model2[:Long, :x] @test length(b) == length(years_variable) @test length(x) == length(years_variable) -@test all(ismissing, b[dim_variable[early_last]+1 : end]) -@test all(iszero, x[dim_variable[early_last]+1 : end]) +# +# These are no longer correct since add_comp! ignores first and last keywords +# +# @test all(ismissing, b[dim_variable[early_last]+1 : end]) +# @test all(iszero, x[dim_variable[early_last]+1 : end]) -# Test the values are right after the late start -@test b[1 : dim_variable[early_last]] == - x[1 : dim_variable[early_last]] == - [2 * i for i in 1:dim_variable[early_last]] +# # Test the values are right after the late start +# @test b[1 : dim_variable[early_last]] == +# x[1 : dim_variable[early_last]] == +# [2 * i for i in 1:dim_variable[early_last]] #------------------------------------------------------------------------------ @@ -125,7 +133,7 @@ regions = [:A, :B] model3 = Model() set_dimension!(model3, :time, years) set_dimension!(model3, :regions, regions) -add_comp!(model3, Short_multi; first=late_start) +add_comp!(model3, Short_multi) #; first=late_start) add_comp!(model3, Long_multi) set_param!(model3, :Short_multi, :a, [1,2]) connect_param!(model3, :Long_multi, :x, :Short_multi, :b, zeros(length(years), length(regions))) @@ -142,15 +150,18 @@ x = model3[:Long_multi, :x] @test size(b) == (length(years), length(regions)) @test size(x) == (length(years), length(regions)) -@test all(ismissing, b[1:year_dim[late_start]-1, :]) -@test all(iszero, x[1:year_dim[late_start]-1, :]) +# +# No longer correct without first/last keywords +# +# @test all(ismissing, b[1:year_dim[late_start]-1, :]) +# @test all(iszero, x[1:year_dim[late_start]-1, :]) -# Test the values are right after the late start -late_yr_idxs = year_dim[late_start]:year_dim[end] +# # Test the values are right after the late start +# late_yr_idxs = year_dim[late_start]:year_dim[end] -@test b[late_yr_idxs, :] == x[year_dim[late_start]:end, :] +# @test b[late_yr_idxs, :] == x[year_dim[late_start]:end, :] -@test b[late_yr_idxs, :] == [[i + 1 for i in late_yr_idxs] [i + 2 for i in late_yr_idxs]] +# @test b[late_yr_idxs, :] == [[i + 1 for i in late_yr_idxs] [i + 2 for i in late_yr_idxs]] #------------------------------------------------------------------------------ # 4. Test where the short component starts late and ends early @@ -161,7 +172,7 @@ first, last = 2002, 2007 model4 = Model() set_dimension!(model4, :time, years) set_dimension!(model4, :regions, regions) -add_comp!(model4, Short_multi; first=first, last=last) +add_comp!(model4, Short_multi) #; first=first, last=last) add_comp!(model4, Long_multi) set_param!(model4, :Short_multi, :a, [1,2]) @@ -179,10 +190,13 @@ x = model4[:Long_multi, :x] @test size(b) == (length(years), length(regions)) @test size(x) == (length(years), length(regions)) -@test all(ismissing, b[1:year_dim[first]-1, :]) -@test all(ismissing, b[year_dim[last]+1:end, :]) -@test all(iszero, x[1:year_dim[first]-1, :]) -@test all(iszero, x[year_dim[last]+1:end, :]) +# +# No longer correct without first/last keywords +# +# @test all(ismissing, b[1:year_dim[first]-1, :]) +# @test all(ismissing, b[year_dim[last]+1:end, :]) +# @test all(iszero, x[1:year_dim[first]-1, :]) +# @test all(iszero, x[year_dim[last]+1:end, :]) # Test the values are right after the late start yr_idxs = year_dim[first]:year_dim[last] @@ -198,17 +212,17 @@ late_start_long = 2002 model5 = Model() set_dimension!(model5, :time, years) -add_comp!(model5, Short; first = late_start) -add_comp!(model5, Long; first = late_start_long) # starts later as well, so backup data needs to match this size +add_comp!(model5, Short) # ; first = late_start) +add_comp!(model5, Long) #; first = late_start_long) # starts later as well, so backup data needs to match this size set_param!(model5, :Short, :a, 2) # A. test wrong size (needs to be length of component, not length of model) -@test_throws ErrorException connect_param!(model5, :Long=>:x, :Short=>:b, zeros(length(years))) +# @test_throws ErrorException connect_param!(model5, :Long=>:x, :Short=>:b, zeros(length(years))) @test_throws ErrorException connect_param!(model4, :Long_multi=>:x, :Short_multi=>:b, zeros(length(years), length(regions)+1)) # test case with >1 dimension # B. test no backup data provided -@test_throws ErrorException connect_param!(model5, :Long=>:x, :Short=>:b) # Error because no backup data provided +# @test_throws ErrorException connect_param!(model5, :Long=>:x, :Short=>:b) # Error because no backup data provided #------------------------------------------------------------------------------ @@ -227,7 +241,7 @@ end model6 = Model() set_dimension!(model6, :time, years) add_comp!(model6, foo, :Long; exports=[:var => :long_var]) -add_comp!(model6, foo, :Short; exports=[:var => :short_var], first=late_start) +add_comp!(model6, foo, :Short; exports=[:var => :short_var]) #, first=late_start) connect_param!(model6, :Short => :par, :Long => :var) set_param!(model6, :Long, :par, years) @@ -241,7 +255,7 @@ short_var = model6[:Short, :var] @test short_par == years # The parameter has values instead of `missing` for years when this component doesn't run, # because they are coming from the longer component that did run -@test all(ismissing, short_var[1:year_dim[late_start]-1]) +# @test all(ismissing, short_var[1:year_dim[late_start]-1]) @test short_var[year_dim[late_start]:end] == years[year_dim[late_start]:end] diff --git a/test/test_datum_storage.jl b/test/test_datum_storage.jl index de7c73be8..2b6151ac3 100644 --- a/test/test_datum_storage.jl +++ b/test/test_datum_storage.jl @@ -3,10 +3,16 @@ module TestDatumStorage using Mimi using Test +comp_first = 2003 +comp_last = 2008 + @defcomp foo begin v = Variable(index = [time]) function run_timestep(p, v, d, ts) - v.v[ts] = gettime(ts) + # implement "short component" via time checking + if comp_first <= gettime(ts) <= comp_last + v.v[ts] = gettime(ts) + end end end @@ -15,6 +21,7 @@ end v = Variable(index = [time, region]) function run_timestep(p, v, d, ts) # v.v[ts, 1:end] = gettime(ts) + for d in d.region v.v[ts, d] = gettime(ts) end @@ -23,9 +30,9 @@ end years = 2001:2010 regions = [:A, :B] -comp_first = 2003 -comp_last = 2008 +nyears = length(years) +nregions = length(regions) #------------------------------------------------------------------------------ # 1. Single dimension case, fixed timesteps @@ -33,11 +40,14 @@ comp_last = 2008 m = Model() set_dimension!(m, :time, years) -add_comp!(m, foo, first=comp_first, last=comp_last) +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, foo, first=comp_first, last=comp_last) +) run(m) v = m[:foo, :v] -@test length(v) == length(years) # Test that the array allocated for variable v is the full length of the time dimension +@test length(v) == nyears # Test that the array allocated for variable v is the full length of the time dimension # Test that the missing values were filled in before/after the first/last values for (i, y) in enumerate(years) @@ -54,20 +64,39 @@ end m2 = Model() +@defcomp baz begin + region = Index() + v = Variable(index = [time, region]) + function run_timestep(p, v, d, ts) + # v.v[ts, 1:end] = gettime(ts) + + # implement "short component" via time checking + if comp_first <= gettime(ts) <= comp_last + for d in d.region + v.v[ts, d] = gettime(ts) + end + end + end +end + set_dimension!(m2, :time, years) set_dimension!(m2, :region, regions) -add_comp!(m2, bar, first=comp_first, last=comp_last) + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m2, baz, first=comp_first, last=comp_last) +) run(m2) -v2 = m2[:bar, :v] -@test size(v2) == (length(years), length(regions)) # Test that the array allocated for variable v is the full length of the time dimension +v2 = m2[:baz, :v] +@test size(v2) == (nyears, nregions) # Test that the array allocated for variable v is the full length of the time dimension # Test that the missing values were filled in before/after the first/last values for (i, y) in enumerate(years) if y < comp_first || y > comp_last - [@test ismissing(v2[i, j]) for j in 1:length(regions)] + [@test ismissing(v2[i, j]) for j in 1:nregions] else - [@test v2[i, j]==y for j in 1:length(regions)] + [@test v2[i, j]==y for j in 1:nregions] end end @@ -77,19 +106,34 @@ end #------------------------------------------------------------------------------ years_variable = [2000:2004..., 2005:5:2030...] -last = 2010 +foo2_first = 2003 +foo2_last = 2010 m = Model() set_dimension!(m, :time, years_variable) -add_comp!(m, foo, first=comp_first, last=last) + +@defcomp foo2 begin + v = Variable(index = [time]) + function run_timestep(p, v, d, ts) + # implement "short component" via time checking + if foo2_first <= gettime(ts) <= foo2_last + v.v[ts] = gettime(ts) + end + end +end + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, foo2, first=foo2_first, last=foo2_last) +) run(m) -v = m[:foo, :v] +v = m[:foo2, :v] @test length(v) == length(years_variable) # Test that the array allocated for variable v is the full length of the time dimension # Test that the missing values were filled in before/after the first/last values for (i, y) in enumerate(years_variable) - if y < comp_first || y > last + if y < foo2_first || y > foo2_last @test ismissing(v[i]) else @test v[i] == y @@ -102,22 +146,38 @@ end m2 = Model() +buz_first = 2003 +buz_last = 2010 + +@defcomp buz begin + region = Index() + v = Variable(index = [time, region]) + function run_timestep(p, v, d, ts) + # v.v[ts, 1:end] = gettime(ts) + + # implement "short component" via time checking + if buz_first <= gettime(ts) <= buz_last + for d in d.region + v.v[ts, d] = gettime(ts) + end + end + end +end + set_dimension!(m2, :time, years_variable) set_dimension!(m2, :region, regions) -add_comp!(m2, bar, first=comp_first, last=last) - +add_comp!(m2, buz) run(m2) -v2 = m2[:bar, :v] -@test size(v2) == (length(years_variable), length(regions)) # Test that the array allocated for variable v is the full length of the time dimension +v2 = m2[:buz, :v] +@test size(v2) == (length(years_variable), nregions) # Test that the array allocated for variable v is the full length of the time dimension # Test that the missing values were filled in before/after the first/last values for (i, y) in enumerate(years_variable) - if y < comp_first || y > last - [@test ismissing(v2[i, j]) for j in 1:length(regions)] + if y < buz_first || y > buz_last + [@test ismissing(v2[i, j]) for j in 1:nregions] else - [@test v2[i, j]==y for j in 1:length(regions)] + [@test v2[i, j]==y for j in 1:nregions] end end - end # module \ No newline at end of file diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index cb47aad7c..ce081c563 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -4,7 +4,8 @@ using Mimi using Test import Mimi: - compdef, AbstractDimension, RangeDimension, Dimension, key_type, first_period, last_period + compdef, AbstractDimension, RangeDimension, Dimension, key_type, first_period, last_period, + ComponentReference dim_varargs = Dimension(:foo, :bar, :baz) # varargs dim_vec = Dimension([:foo, :bar, :baz]) # Vector @@ -90,20 +91,27 @@ end m = Model() set_dimension!(m, :time, 2000:2100) -@test_throws ErrorException add_comp!(m, foo2; first = 2005, last = 2105) # Can't add a component longer than a model -foo2_ref = add_comp!(m, foo2; first = 2005, last = 2095) + +# First and last have been disabled... +#@test_throws ErrorException add_comp!(m, foo2; first = 2005, last = 2105) # Can't add a component longer than a model + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + foo2_ref = add_comp!(m, foo2; first = 2005, last = 2095) +) + +foo2_ref = ComponentReference(m, :foo2) my_foo2 = compdef(foo2_ref) +# First and last have been disabled... # Can't set time more narrowly than components are defined as -@test_throws ErrorException set_dimension!(m, :time, 1990:2200) - -@test first_period(my_foo2) == 2005 -@test last_period(my_foo2) == 2095 +# @test_throws ErrorException set_dimension!(m, :time, 1990:2200) +# @test first_period(my_foo2) == 2005 +# @test last_period(my_foo2) == 2095 # Test parameter connections -@test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long - -set_param!(m, :foo2, :x, 2005:2095) # Shouldn't throw an error +# @test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long +# set_param!(m, :foo2, :x, 2005:2095) # Shouldn't throw an error set_dimension!(m, :time, 2010:2050) diff --git a/test/test_explorer.jl b/test/test_explorer.jl index 462123cb0..1abc55357 100644 --- a/test/test_explorer.jl +++ b/test/test_explorer.jl @@ -49,7 +49,7 @@ run(m) @test typeof(dataframe_or_scalar(m, :MyComp, :d)) == Float64 #2. JSON strings for the spec "values" key -# TODO: getmultiline, getline, getbar, getdatapart +#: getmultiline, getline, getbar, getdatapart #3. full specs for VegaLit diff --git a/test/test_getdataframe.jl b/test/test_getdataframe.jl index 149be6cc3..3c527380a 100644 --- a/test/test_getdataframe.jl +++ b/test/test_getdataframe.jl @@ -18,32 +18,40 @@ model1 = Model() var1 = Variable(index=[time]) par1 = Parameter(index=[time]) par_scalar = Parameter() - + function run_timestep(p, v, d, t) v.var1[t] = p.par1[t] end end +late_first = 2030 +early_last = 2100 + @defcomp testcomp2 begin var2 = Variable(index=[time]) par2 = Parameter(index=[time]) - + function run_timestep(p, v, d, t) - v.var2[t] = p.par2[t] + if late_first <= gettime(t) <= early_last # apply time constraints in the component + v.var2[t] = p.par2[t] + end end end years = collect(2015:5:2110) -late_first = 2030 -early_last = 2100 set_dimension!(model1, :time, years) add_comp!(model1, testcomp1) set_param!(model1, :testcomp1, :par1, years) set_param!(model1, :testcomp1, :par_scalar, 5.) -add_comp!(model1, testcomp2; first = late_first, last = early_last) -set_param!(model1, :testcomp2, :par2, late_first:5:early_last) +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(model1, testcomp2; first = late_first, last = early_last) +) + +@test_throws ErrorException set_param!(model1, :testcomp2, :par2, late_first:5:early_last) +set_param!(model1, :testcomp2, :par2, years) # Test running before model built @test_throws ErrorException df = getdataframe(model1, :testcomp1, :var1) @@ -59,10 +67,12 @@ run(model1) # Regular getdataframe df = getdataframe(model1, :testcomp1=>:var1, :testcomp1=>:par1, :testcomp2=>:var2, :testcomp2=>:par2) + dim = Mimi.dimension(model1, :time) @test df[:var1] == df[:par1] == years @test all(ismissing, df[:var2][1 : dim[late_first]-1]) -@test all(ismissing, df[:par2][1 : dim[late_first]-1]) +#@test all(ismissing, df[:par2][1 : dim[late_first]-1]) + @test df[:var2][dim[late_first] : dim[early_last]] == df[:par2][dim[late_first] : dim[early_last]] == late_first:5:early_last @test all(ismissing, df[:var2][dim[years[end]] : dim[early_last]]) @test all(ismissing, df[:par2][dim[years[end]] : dim[early_last]]) @@ -74,15 +84,20 @@ dim = Mimi.dimension(model1, :time) #------------------------------------------------------------------------------ # 2. Test with > 2 dimensions #------------------------------------------------------------------------------ +stepsize = 5 +years = collect(2015:stepsize:2110) +regions = [:reg1, :reg2] +rates = [0.025, 0.05] + +nyears = length(years) +nregions = length(regions) +nrates = length(rates) @defcomp testcomp3 begin par3 = Parameter(index=[time, regions, rates]) var3 = Variable(index=[time]) end -regions = [:reg1, :reg2] -rates = [0.025, 0.05] - # A. Simple case where component has same time length as model model2 = Model() @@ -91,8 +106,8 @@ set_dimension!(model2, :time, years) set_dimension!(model2, :regions, regions) set_dimension!(model2, :rates, rates) -data = Array{Int}(undef, length(years), length(regions), length(rates)) -data[:] = 1:(length(years) * length(regions) * length(rates)) +data = Array{Int}(undef, nyears, nregions, nrates) +data[:] = 1:(nyears * nregions * nrates) add_comp!(model2, testcomp3) set_param!(model2, :testcomp3, :par3, data) @@ -106,24 +121,44 @@ df2 = getdataframe(model2, :testcomp3, :par3) @test_throws ErrorException getdataframe(model2, Pair(:testcomp3, :par3), Pair(:testcomp3, :var3)) -# B. Test with shorter time than model +# B. Test with shorter time than model model3 = Model() set_dimension!(model3, :time, years) set_dimension!(model3, :regions, regions) set_dimension!(model3, :rates, rates) -add_comp!(model3, testcomp3; first = late_first, last = early_last) -par3 = Array{Float64}(undef, length(late_first:5:early_last), length(regions), length(rates)) -par3[:] = 1:(length(late_first:5:early_last) * length(regions) * length(rates)) +dim = Mimi.dimension(model3, :time) + +late_first = 2030 +early_last = 2100 + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(model3, testcomp3; first = late_first, last = early_last) +) + +indices = collect(late_first:stepsize:early_last) +nindices = length(indices) + +valid_indices = collect(dim[late_first]:dim[early_last]) +nvalid = length(valid_indices) + +par3 = Array{Union{Missing,Float64}}(undef, nyears, nregions, nrates) +par3[:] .= missing + +par3[valid_indices, :, :] = 1:(nindices * nregions * nrates) set_param!(model3, :testcomp3, :par3, par3) run(model3) -df3 = getdataframe(model3, :testcomp3=>:par3) -@test size(df3) == (length(rates)*length(regions)*length(years), 4) +df3 = getdataframe(model3, :testcomp3 => :par3) +@test size(df3) == (nrates * nregions * nyears, 4) # Test that times outside the component's time span are padded with `missing` values -@test all(ismissing, df3[:par3][1 : (length(rates)*length(regions)*(dim[late_first]-1))]) -@test all(ismissing, df3[:par3][end - (length(rates)*length(regions)*(dim[end]-dim[early_last]))+1: end]) +@test all(ismissing, df3[:par3][1 : (nrates * nregions * (dim[late_first]-1))]) + +nmissing = (Int((years[end] - early_last)/stepsize) * nregions * nrates - 1) + +@test all(ismissing, df3[:par3][end - nmissing : end]) end #module diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 31572e62d..4d865558e 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -6,7 +6,7 @@ using Test using Mimi import Mimi: - connect_param!, unconnected_params, set_dimension!, + connect_param!, unconnected_params, set_dimension!, has_comp, reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, dim_names, compdef, getproperty, setproperty!, dimension, compdefs @@ -49,11 +49,26 @@ last_A = 2150 m = Model() set_dimension!(m, :time, years) -@test_throws ErrorException add_comp!(m, A, last = 2210) -@test_throws ErrorException add_comp!(m, A, first = 2010) +# first and last are now disabled +# @test_throws ErrorException add_comp!(m, A, last = 2210) +# @test_throws ErrorException add_comp!(m, A, first = 2010) + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, A, last = 2210) +) + +# remove the comp we just added so later tests succeed +delete!(m, :A) +@test has_comp(m, :A) == false + @test_throws ArgumentError add_comp!(m, A, after=:B) # @test_throws ErrorException add_comp!(m, A, after=:B) -add_comp!(m, A, first = first_A, last = last_A) #test specific last and first + +@test_logs( + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, A, first = first_A, last = last_A) #test specific last and first +) add_comp!(m, B, before=:A) diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index ad1eaafb0..b6d860e36 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -50,6 +50,8 @@ end # Check that explicit number type for model works as expected numtype = Float32 +arrtype = Union{Missing, numtype} + m = Model(numtype) set_dimension!(m, :time, 2000:2100) @@ -76,11 +78,11 @@ extpars = external_params(m) @test isa(extpars[:e], ArrayModelParameter) @test isa(extpars[:f], ScalarModelParameter) # note that :f is stored as a scalar parameter even though its values are an array -@test typeof(extpars[:a].values) == TimestepMatrix{FixedTimestep{2000, 1}, numtype} -@test typeof(extpars[:b].values) == TimestepVector{FixedTimestep{2000, 1}, numtype} -@test typeof(extpars[:c].values) == Array{numtype, 1} +@test typeof(extpars[:a].values) == TimestepMatrix{FixedTimestep{2000, 1}, arrtype} +@test typeof(extpars[:b].values) == TimestepVector{FixedTimestep{2000, 1}, arrtype} +@test typeof(extpars[:c].values) == Array{arrtype, 1} @test typeof(extpars[:d].value) == numtype -@test typeof(extpars[:e].values) == Array{numtype, 1} +@test typeof(extpars[:e].values) == Array{arrtype, 1} @test typeof(extpars[:f].value) == Array{Float64, 2} @test typeof(extpars[:g].value) <: Int @test typeof(extpars[:h].value) == numtype @@ -99,9 +101,9 @@ update_param!(m, :d, 5) # should work, will convert to float update_param!(m, :e, [4,5,6,7]) @test length(extpars) == 8 -@test typeof(extpars[:a].values) == TimestepMatrix{FixedTimestep{2000, 1}, numtype} +@test typeof(extpars[:a].values) == TimestepMatrix{FixedTimestep{2000, 1}, arrtype} @test typeof(extpars[:d].value) == numtype -@test typeof(extpars[:e].values) == Array{numtype, 1} +@test typeof(extpars[:e].values) == Array{arrtype, 1} #------------------------------------------------------------------------------ @@ -120,18 +122,18 @@ end m = Model() set_dimension!(m, :time, 2000:2002) -add_comp!(m, MyComp2; first=2000, last=2002) +add_comp!(m, MyComp2) # ; first=2000, last=2002) set_param!(m, :MyComp2, :x, [1, 2, 3]) -@test_logs( - (:warn, "Redefining dimension :time"), - # (:warn, "Resetting MyComp2 component's first timestep to 2001"), - set_dimension!(m, :time, 2001:2003) -) +# N.B. `first` and `last` are now disabled. +# Can't move last beyond last for a component +# @test_throws ErrorException set_dimension!(m, :time, 2001:2003) + +set_dimension!(m, :time, 2001:2002) update_param!(m, :x, [4, 5, 6], update_timesteps = false) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2000, 1, LAST} where LAST, Float64, 1} +@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2000, 1, LAST} where LAST, Union{Missing,Float64}, 1} @test x.values.data == [4., 5., 6.] # TBD: this fails, but I'm not sure how it's supposed to behave. It says: # (ERROR: BoundsError: attempt to access 3-element Array{Float64,1} at index [4]) @@ -139,10 +141,10 @@ x = external_param(m.md, :x) # @test m[:MyComp2, :y][1] == 5 # 2001 # @test m[:MyComp2, :y][2] == 6 # 2002 -update_param!(m, :x, [2, 3, 4], update_timesteps = true) +update_param!(m, :x, [2, 3], update_timesteps = true) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2001, 1, LAST} where LAST, Float64, 1} -@test x.values.data == [2., 3., 4.] +@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2001, 1, LAST} where LAST, Union{Missing,Float64}, 1} +@test x.values.data == [2., 3.] run(m) @test m[:MyComp2, :y][1] == 2 # 2001 @test m[:MyComp2, :y][2] == 3 # 2002 @@ -152,18 +154,18 @@ run(m) m = Model() set_dimension!(m, :time, [2000, 2005, 2020]) -add_comp!(m, MyComp2; first=2000, last=2020) -set_param!(m, :MyComp2, :x, [1, 2, 3]) @test_logs( - (:warn, "Redefining dimension :time"), - # (:warn, "Resetting MyComp2 component's first timestep to 2005"), - set_dimension!(m, :time, [2005, 2020, 2050]) + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), + add_comp!(m, MyComp2; first=2000, last=2020) ) +set_param!(m, :MyComp2, :x, [1, 2, 3]) + +set_dimension!(m, :time, [2005, 2020, 2050]) update_param!(m, :x, [4, 5, 6], update_timesteps = false) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2005, 2020)}, Float64, 1} +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2005, 2020)}, Union{Missing,Float64}, 1} @test x.values.data == [4., 5., 6.] #run(m) #@test m[:MyComp2, :y][1] == 5 # 2005 @@ -171,7 +173,7 @@ x = external_param(m.md, :x) update_param!(m, :x, [2, 3, 4], update_timesteps = true) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Float64, 1} +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] run(m) @test m[:MyComp2, :y][1] == 2 # 2005 @@ -184,15 +186,12 @@ m = Model() set_dimension!(m, :time, [2000, 2005, 2020]) add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -@test_logs( - (:warn, "Redefining dimension :time"), - # (:warn, "Resetting MyComp2 component's first timestep to 2005"), - set_dimension!(m, :time, [2005, 2020, 2050]) -) + +set_dimension!(m, :time, [2005, 2020, 2050]) update_params!(m, Dict(:x=>[2, 3, 4]), update_timesteps = true) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Float64, 1} +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] run(m) @test m[:MyComp2, :y][1] == 2 # 2005 @@ -207,18 +206,17 @@ set_dimension!(m, :time, 2000:2002) # length 3 add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -@test_logs (:warn, "Redefining dimension :time") set_dimension!(m, :time, 1999:2003) # length 5 +set_dimension!(m, :time, 1999:2003) # length 5 @test_throws ErrorException update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = false) update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = true) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, LAST} where LAST, Float64, 1} +@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, LAST} where LAST, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4., 5., 6.] run(m) @test m[:MyComp2, :y] == [2., 3., 4., 5., 6.] - # 5. Test all the warning and error cases @defcomp MyComp3 begin @@ -245,14 +243,11 @@ update_param!(m, :z, 1) @test external_param(m.md, :z).value == 1 # Reset the time dimensions -@test_logs( - (:warn, "Redefining dimension :time"), - # (:warn, "Resetting MyComp3 component's first timestep to 2005"), - set_dimension!(m, :time, 2005:2007) -) +set_dimension!(m, :time, 2005:2007) + update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0), update_timesteps=true) # Won't error when updating from a dictionary -@test external_param(m.md, :x).values isa Mimi.TimestepArray{Mimi.FixedTimestep{2005,1},Float64,1} +@test external_param(m.md, :x).values isa Mimi.TimestepArray{Mimi.FixedTimestep{2005,1},Union{Missing,Float64},1} @test external_param(m.md, :x).values.data == [3.,4.,5.] @test external_param(m.md, :y).values == [10.,20.] @test external_param(m.md, :z).value == 0 diff --git a/test/test_plotting.jl b/test/test_plotting.jl index 8b326a160..41a9663e2 100644 --- a/test/test_plotting.jl +++ b/test/test_plotting.jl @@ -31,8 +31,8 @@ m = Model() set_dimension!(m, :time, 2000:3000) nsteps = Mimi.dim_count(m.md, :time) -add_comp!(m, ShortComponent; first=2100) -add_comp!(m, LongComponent; first=2000) +add_comp!(m, ShortComponent) #; first=2100) +add_comp!(m, LongComponent) #; first=2000) set_param!(m, :ShortComponent, :a, 2.) set_param!(m, :LongComponent, :y, 1.) diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index c3ee572ac..c0d06d171 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -106,11 +106,12 @@ first_foo = 2005 m = Model() set_dimension!(m, :time, years) +# first and last disabled # test that you can only add components with first/last within model's time index range -@test_throws ErrorException add_comp!(m, Foo; first=1900) -@test_throws ErrorException add_comp!(m, Foo; last=2100) +# @test_throws ErrorException add_comp!(m, Foo; first=1900) +# @test_throws ErrorException add_comp!(m, Foo; last=2100) -foo = add_comp!(m, Foo; first=first_foo) #offset for foo +foo = add_comp!(m, Foo) # DISABLED: first=first_foo) # offset for foo bar = add_comp!(m, Bar; exports=[:output => :bar_output]) set_param!(m, :Foo, :inputF, 5.) @@ -158,7 +159,7 @@ t_matrix = get_timestep_array(m.md, Float64, 2, matrix) # try with variable timestep set_dimension!(m, :time, [2000:1:2004; 2005:2:2009]) -t_vector= get_timestep_array(m.md, Float64, 1, vector) +t_vector = get_timestep_array(m.md, Float64, 1, vector) t_matrix = get_timestep_array(m.md, Float64, 2, matrix) @test typeof(t_vector) <: TimestepVector From 4cc1a8ccb37e1d7cb3c6f737757ece5b1261ffb8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 3 May 2019 11:09:42 -0700 Subject: [PATCH 51/81] Consolidated some timestep functions --- src/core/time.jl | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/core/time.jl b/src/core/time.jl index 99f767052..52e1cecae 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -279,11 +279,7 @@ end # int indexing version supports old-style components and internal functions, not # part of the public API -function Base.setindex!(v::TimestepVector{FixedTimestep{Start, STEP}, T}, val, i::AnyIndex) where {T, Start, STEP} - setindex!(v.data, val, i) -end - -function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, i::AnyIndex) where {T, TIMES} +function Base.setindex!(v::TimestepVector, val, i::AnyIndex) setindex!(v.data, val, i) end @@ -322,11 +318,7 @@ end # int indexing version supports old-style components and internal functions, not # part of the public API -function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, idx1::AnyIndex, idx2::AnyIndex) where {T, FIRST, STEP} - return mat.data[idx1, idx2] -end - -function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, idx1::AnyIndex, idx2::AnyIndex) where {T, TIMES} +function Base.getindex(mat::TimestepMatrix, idx1::AnyIndex, idx2::AnyIndex) return mat.data[idx1, idx2] end @@ -351,19 +343,11 @@ end # int indexing version supports old-style components and internal functions, not # part of the public API -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, val, idx1::Int, idx2::Int) where {T, FIRST, STEP} - setindex!(mat.data, val, idx1, idx2) -end - -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, val, idx1::AnyIndex, idx2::AnyIndex) where {T, FIRST, STEP} - mat.data[idx1,idx2] .= val -end - -function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, val, idx1::Int, idx2::Int) where {T, TIMES} +function Base.setindex!(mat::TimestepMatrix, val, idx1::Int, idx2::Int) setindex!(mat.data, val, idx1, idx2) end -function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, val, idx1::AnyIndex, idx2::AnyIndex) where {T, TIMES} +function Base.setindex!(mat::TimestepMatrix, val, idx1::AnyIndex, idx2::AnyIndex) mat.data[idx1,idx2] .= val end @@ -371,6 +355,9 @@ end # 4. TimestepArray methods # +# Enables broadcast assignment +Base.dotview(v::Mimi.TimestepArray, args...) = Base.dotview(v.data, args...) + Base.fill!(obj::TimestepArray, value) = fill!(obj.data, value) Base.size(obj::TimestepArray) = size(obj.data) @@ -411,11 +398,7 @@ end # int indexing version supports old-style components and internal functions, not # part of the public API; first index is Int or Range, rather than a Timestep -function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, FIRST, STEP} - return arr.data[idx1, idx2, idxs...] -end - -function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, TIMES} +function Base.getindex(arr::TimestepArray, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) return arr.data[idx1, idx2, idxs...] end From a2a3c38641aba867c4ea0d7cceb078b8ed174ad1 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 8 May 2019 12:08:30 -0700 Subject: [PATCH 52/81] WIP - debugging model pkgs --- src/core/connections.jl | 10 ++++++++-- src/core/model.jl | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index ea2aae307..72a7f18e1 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -309,6 +309,8 @@ function unconnected_params(md::ModelDef) return unconnected end +global ARGS = nothing + """ set_leftover_params!(m::Model, parameters::Dict) @@ -317,6 +319,7 @@ to some other component to a value from a dictionary `parameters`. This method a the dictionary keys are strings that match the names of unset parameters in the model. """ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T + ARGS = (md, parameters) parameters = Dict(k => v for (k, v) in parameters) for (comp_path, param_name) in unconnected_params(md) @@ -411,7 +414,7 @@ Add a one dimensional time-indexed array parameter indicated by `name` and """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::TimestepVector, dims) - # println("set_external_array_param!: dims=$dims, setting dims to [:time]") + # @info "1. set_external_array_param!: name=$name value=$value dims=$dims, setting dims to [:time]" param = ArrayModelParameter(value, [:time]) # must be :time set_external_param!(obj, name, param) end @@ -425,6 +428,7 @@ Add a multi-dimensional time-indexed array parameter `name` with value """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::TimestepArray, dims) + # @info "2. set_external_array_param!: name=$name value=$value dims=$dims" param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims) set_external_param!(obj, name, param) end @@ -437,8 +441,10 @@ Add an array type parameter `name` with value `value` and `dims` dimensions to t """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::AbstractArray, dims) + # @info "3. set_external_array_param!: name=$name value=$value dims=$dims" numtype = Union{Missing, number_type(obj)} - if !(typeof(value) <: Array{numtype}) + + if !(typeof(value) <: Array{numtype} || (value isa AbstractArray && eltype(value) <: numtype)) # Need to force a conversion (simple convert may alias in v0.6) value = Array{numtype}(undef, value) end diff --git a/src/core/model.jl b/src/core/model.jl index e0de76deb..191874851 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -55,6 +55,8 @@ component parameter should only be calculated for the second timestep and beyond backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) => md +@delegate connect_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) => md + """ connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, backup::Array; ignoreunits::Bool=false) From d8ffd5da639b2e63b5f846055bf65ce980eb49f4 Mon Sep 17 00:00:00 2001 From: Cora Kingdon Date: Wed, 8 May 2019 15:42:18 -0400 Subject: [PATCH 53/81] Fix logic in set_leftover_params! --- src/core/connections.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 72a7f18e1..7a99fa1d1 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -327,7 +327,7 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T comp_name = nameof(comp_def) # check whether we need to set the external parameter - if external_param(md, param_name, missing_ok=true) !== nothing + if external_param(md, param_name, missing_ok=true) == nothing value = parameters[string(param_name)] param_dims = parameter_dimensions(md, comp_name, param_name) From 4eda904d7786087c59e6367fa6cf774950470a27 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 8 May 2019 13:15:13 -0700 Subject: [PATCH 54/81] WIP - testing model packages --- src/core/connections.jl | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 7a99fa1d1..9163c2e29 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -265,23 +265,10 @@ function connected_params(md::ModelDef) return connected end -""" - connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) - -Return list of parameters that have been set for component `comp_name` in composite `obj`. -""" -# Deprecated -# function connected_params(obj::AbstractCompositeComponentDef, comp_name::Symbol) -# ext_set_params = map(x->x.param_name, external_param_conns(obj, comp_name)) -# int_set_params = map(x->x.dst_par_name, internal_param_conns(obj, comp_name)) - -# return union(ext_set_params, int_set_params) -# end - """ Depth-first search for unconnected parameters, which are appended to `unconnected`. Parameter connections are made to the "original" component, not to a composite that exports the parameter. -Thus, only the leaf (non-composite) variant of this method actually collects uncollected params. +Thus, only the leaf (non-composite) variant of this method actually collects unconnected params. """ function _collect_unconnected_params(obj::ComponentDef, connected::ParamVector, unconnected::ParamVector) comp_path = obj.comp_path @@ -309,8 +296,6 @@ function unconnected_params(md::ModelDef) return unconnected end -global ARGS = nothing - """ set_leftover_params!(m::Model, parameters::Dict) @@ -319,15 +304,12 @@ to some other component to a value from a dictionary `parameters`. This method a the dictionary keys are strings that match the names of unset parameters in the model. """ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T - ARGS = (md, parameters) - parameters = Dict(k => v for (k, v) in parameters) - for (comp_path, param_name) in unconnected_params(md) comp_def = compdef(md, comp_path) comp_name = nameof(comp_def) # check whether we need to set the external parameter - if external_param(md, param_name, missing_ok=true) == nothing + if external_param(md, param_name, missing_ok=true) === nothing value = parameters[string(param_name)] param_dims = parameter_dimensions(md, comp_name, param_name) From 1913093228744a29229abeafaacc7b9e564ca7f8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 8 May 2019 14:37:22 -0700 Subject: [PATCH 55/81] - Added Classes to Project.toml - Removed redundant method def --- Project.toml | 1 + REQUIRE | 26 -------------------------- src/core/time.jl | 2 -- 3 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 REQUIRE diff --git a/Project.toml b/Project.toml index 25c4a5bcd..f428b3468 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ version = "0.8.7-DEV" [deps] CSVFiles = "5d742f6a-9f54-50ce-8119-2520741973ca" +Classes = "1a9c1350-211b-5766-99cd-4544d885a0d1" Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index 3f0dce108..000000000 --- a/REQUIRE +++ /dev/null @@ -1,26 +0,0 @@ -julia 1.1 -Classes -Compose -CSVFiles -DataFrames -DataStructures -Distributions -Electron -ExcelFiles -IterTools -IterableTables -FileIO -GraphPlot 0.3.0 -GlobalSensitivityAnalysis -JSON -LightGraphs -MacroTools -MetaGraphs -NamedArrays -StatsBase -StringBuilders -TableTraits -ProgressMeter -Query -VegaLite -FilePaths diff --git a/src/core/time.jl b/src/core/time.jl index 3a530f20c..bbd34bbbb 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -354,8 +354,6 @@ end # # 4. TimestepArray methods # -Base.dotview(v::Mimi.TimestepArray, args...) = Base.dotview(v.data, args...) - # Enables broadcast assignment Base.dotview(v::Mimi.TimestepArray, args...) = Base.dotview(v.data, args...) From fb754049a6883ec5b48edeebab49f485e790aa91 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 14 May 2019 10:36:05 -0700 Subject: [PATCH 56/81] WIP - Debugging composite Dice. And a bit of a file reorg. --- src/Mimi.jl | 9 +- src/core/connections.jl | 69 +--- src/core/defcomposite.jl | 14 +- src/core/defs.jl | 49 ++- src/core/order.jl | 64 ++++ src/core/paths.jl | 8 +- src/core/time.jl | 312 +--------------- src/core/time_arrays.jl | 307 +++++++++++++++ src/core/types.jl | 717 ------------------------------------ src/core/types/_includes.jl | 6 + src/core/types/core.jl | 95 +++++ src/core/types/defs.jl | 224 +++++++++++ src/core/types/instances.jl | 241 ++++++++++++ src/core/types/model.jl | 44 +++ src/core/types/params.jl | 73 ++++ src/core/types/time.jl | 53 +++ 16 files changed, 1182 insertions(+), 1103 deletions(-) create mode 100644 src/core/order.jl create mode 100644 src/core/time_arrays.jl delete mode 100644 src/core/types.jl create mode 100644 src/core/types/_includes.jl create mode 100644 src/core/types/core.jl create mode 100644 src/core/types/defs.jl create mode 100644 src/core/types/instances.jl create mode 100644 src/core/types/model.jl create mode 100644 src/core/types/params.jl create mode 100644 src/core/types/time.jl diff --git a/src/Mimi.jl b/src/Mimi.jl index ecacd65e9..abeb2f9d0 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -54,9 +54,10 @@ export variable_names include("core/delegate.jl") -include("core/types.jl") - -# After loading types and delegation macro, the rest is alphabetical +include("core/types/_includes.jl") +# +# After loading types and delegation macro, the rest can be loaded in any order. +# include("core/build.jl") include("core/connections.jl") include("core/defs.jl") @@ -67,11 +68,11 @@ include("core/dimensions.jl") include("core/instances.jl") include("core/references.jl") include("core/time.jl") +include("core/time_arrays.jl") include("core/model.jl") include("core/paths.jl") include("core/show.jl") -# For debugging composites we don't need these include("explorer/explore.jl") include("mcs/mcs.jl") include("utils/getdataframe.jl") diff --git a/src/core/connections.jl b/src/core/connections.jl index 9163c2e29..c7bfa75a4 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -140,6 +140,9 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_comp_def = compdef(obj, dst_comp_path) src_comp_def = compdef(obj, src_comp_path) + @info "src_comp_def calling compdef($(obj.comp_id), $src_comp_path)" + src_comp_def === nothing && @info "src_comp_def === nothing" + if backup !== nothing # If value is a NamedArray, we can check if the labels match if isa(backup, NamedArray) @@ -609,69 +612,3 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) return nothing end - - -# -# Support for automatic ordering of components -# - -""" - dependencies(md::ModelDef, comp_path::ComponentPath) - -Return the set of component names that `comp_path` in `md` depends one, i.e., -sources for which `comp_name` is the destination of an internal connection. -""" -function dependencies(md::ModelDef, comp_path::ComponentPath) - conns = internal_param_conns(md) - # For the purposes of the DAG, we don't treat dependencies on [t-1] as an ordering constraint - deps = Set(c.src_comp_path for c in conns if (c.dst_comp_path == comp_path && c.offset == 0)) - return deps -end - -""" - comp_graph(md::ModelDef) - -Return a MetaGraph containing a directed (LightGraph) graph of the components of -ModelDef `md`. Each vertex has a :name property with its component name. -""" -function comp_graph(md::ModelDef) - comp_paths = [c.comp_path for c in compdefs(md)] - graph = MetaDiGraph() - - for comp_path in comp_paths - add_vertex!(graph, :path, comp_path) - end - - set_indexing_prop!(graph, :path) - - for comp_path in comp_paths - for dep_path in dependencies(md, comp_path) - src = graph[dep_path, :path] - dst = graph[comp_path, :path] - add_edge!(graph, src, dst) - end - end - - #TODO: for now we can allow cycles since we aren't using the offset - # if is_cyclic(graph) - # error("Component graph contains a cycle") - # end - - return graph -end - -""" - _topological_sort(md::ModelDef) - -Build a directed acyclic graph referencing the positions of the components in -the OrderedDict of model `md`, tracing dependencies to create the DAG. -Perform a topological sort on the graph for the given model and return a vector -of component paths in the order that will ensure dependencies are processed -prior to dependent components. -""" -function _topological_sort(md::ModelDef) - graph = comp_graph(md) - ordered = topological_sort_by_dfs(graph) - paths = map(i -> graph[i, :path], ordered) - return paths -end diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 676ab1b2a..b9ec77a7c 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -117,6 +117,8 @@ functions; these are defined internally to iterate over constituent components a associated method on each. """ macro defcomposite(cc_name, ex) + @info "defining composite $cc_name in module $(fullname(__module__))" + @capture(ex, elements__) comps = SubComponent[] @@ -128,12 +130,20 @@ macro defcomposite(cc_name, ex) end end + # module_name = nameof(__module__) + + # TBD: use fullname(__module__) to get "path" to module, as tuple of Symbols, e.g., (:Main, :ABC, :DEF) + # TBD: use Base.moduleroot(__module__) to get the first in that sequence, if needed + # TBD: parentmodule(m) gets the enclosing module (but for root modules returns self) + # TBD: might need to replace the single symbol used for module name in ComponentId with Module path. + result = quote - cc_id = Mimi.ComponentId(nameof(@__MODULE__), $(QuoteNode(cc_name))) - global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, @__MODULE__) + cc_id = Mimi.ComponentId($__module__, $(QuoteNode(cc_name))) + global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, $__module__) $cc_name end + # @info "defcomposite:\n$result" return esc(result) end diff --git a/src/core/defs.jl b/src/core/defs.jl index 837dcd905..a44982d04 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,4 +1,24 @@ -compdef(comp_id::ComponentId) = getfield(getfield(Main, comp_id.module_name), comp_id.comp_name) + +function find_module(path::NTuple{N, Symbol} where N) + m = Main + for name in path + try + m = getfield(m, name) + catch + error("Module name $name was not found in module $m") + end + end + return m +end + +function compdef(comp_id::ComponentId; module_obj::Union{Nothing, Module}=nothing) + if module_obj === nothing + path = @or(comp_id.module_path, (:Main, comp_id.module_name)) + module_obj = find_module(path) + end + + return getfield(module_obj, comp_id.comp_name) +end compdef(cr::ComponentReference) = find_comp(cr) @@ -11,7 +31,7 @@ has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_d compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) compkeys(c::AbstractCompositeComponentDef) = keys(c.comps_dict) -# Allows method to be called on leaf component defs, which sometimes simplifies code. +# Allows method to be called harmlessly on leaf component defs, which simplifies recursive funcs. compdefs(c::ComponentDef) = [] compmodule(comp_id::ComponentId) = comp_id.module_name @@ -443,6 +463,31 @@ function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, para return parameter_dimensions(compdef(obj, comp_name), param_name) end +""" + set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, + value_dict::Dict{Symbol, Any}, param_names) + +Call `set_param!()` for each name in `param_names`, retrieving the corresponding value from +`value_dict[param_name`. +""" +function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, value_dict::Dict{Symbol, Any}, param_names) + for param_name in param_names + set_param!(obj, comp_name, value_dict, param_name) + end +end + +""" + set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, + value_dict::Dict{Symbol, Any}, dims=nothing) + +Call `set_param!()` with `param_name` and a value dict in which `value_dict[param_name]` references +the value of parameter `param_name`. +""" +function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, value_dict::Dict{Symbol, Any}, + param_name::Symbol, dims=nothing) + value = value_dict[param_name] + set_param!(obj, comp_name, param_name, value, dims) +end function set_param!(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) # @info "set_param!($(obj.comp_id), $comp_path, $param_name, $value)" diff --git a/src/core/order.jl b/src/core/order.jl new file mode 100644 index 000000000..caca55533 --- /dev/null +++ b/src/core/order.jl @@ -0,0 +1,64 @@ +# +# Support for automatic ordering of components +# + +""" + dependencies(md::ModelDef, comp_path::ComponentPath) + +Return the set of component names that `comp_path` in `md` depends one, i.e., +sources for which `comp_name` is the destination of an internal connection. +""" +function dependencies(md::ModelDef, comp_path::ComponentPath) + conns = internal_param_conns(md) + # For the purposes of the DAG, we don't treat dependencies on [t-1] as an ordering constraint + deps = Set(c.src_comp_path for c in conns if (c.dst_comp_path == comp_path && c.offset == 0)) + return deps +end + +""" + comp_graph(md::ModelDef) + +Return a MetaGraph containing a directed (LightGraph) graph of the components of +ModelDef `md`. Each vertex has a :name property with its component name. +""" +function comp_graph(md::ModelDef) + comp_paths = [c.comp_path for c in compdefs(md)] + graph = MetaDiGraph() + + for comp_path in comp_paths + add_vertex!(graph, :path, comp_path) + end + + set_indexing_prop!(graph, :path) + + for comp_path in comp_paths + for dep_path in dependencies(md, comp_path) + src = graph[dep_path, :path] + dst = graph[comp_path, :path] + add_edge!(graph, src, dst) + end + end + + #TODO: for now we can allow cycles since we aren't using the offset + # if is_cyclic(graph) + # error("Component graph contains a cycle") + # end + + return graph +end + +""" + _topological_sort(md::ModelDef) + +Build a directed acyclic graph referencing the positions of the components in +the OrderedDict of model `md`, tracing dependencies to create the DAG. +Perform a topological sort on the graph for the given model and return a vector +of component paths in the order that will ensure dependencies are processed +prior to dependent components. +""" +function _topological_sort(md::ModelDef) + graph = comp_graph(md) + ordered = topological_sort_by_dfs(graph) + paths = map(i -> graph[i, :path], ordered) + return paths +end diff --git a/src/core/paths.jl b/src/core/paths.jl index 08ac70629..06193b76f 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -39,14 +39,18 @@ function _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) elts = split(path, "/") - if elts[1] == "" # if path starts with "/", elt[1] == "" + if elts[1] == "" # path started with "/" root = get_root(node) elts[1] = string(nameof(root)) end return ComponentPath([Symbol(elt) for elt in elts]) end -find_comp(obj::ComponentDef, path::ComponentPath) = (isempty(path) ? obj : nothing) +# For leaf components, we can only "find" the component itself +# when the path is empty. +function find_comp(obj::ComponentDef, path::ComponentPath) + return isempty(path) ? obj : nothing +end function find_comp(obj::AbstractComponentDef, name::Symbol) # N.B. test here since compdef doesn't check existence diff --git a/src/core/time.jl b/src/core/time.jl index bbd34bbbb..23eb70bf9 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -1,5 +1,5 @@ # -# 1. TIMESTEP +# TIMESTEP # """ @@ -126,7 +126,7 @@ function Base.:+(ts::VariableTimestep{TIMES}, val::Int) where {TIMES} end # -# 2. CLOCK +# CLOCK # function Clock(time_keys::Vector{Int}) @@ -172,311 +172,3 @@ function Base.reset(c::Clock) c.ts = c.ts - (c.ts.t - 1) nothing end - -# -# 3. TimestepVector and TimestepMatrix -# - -# -# 3a. General -# - -# Get a timestep array of type T with N dimensions. Time labels will match those from the time dimension in md -function get_timestep_array(md::ModelDef, T, N, value) - if isuniform(md) - first, stepsize = first_and_step(md) - return TimestepArray{FixedTimestep{first, stepsize}, T, N}(value) - else - TIMES = (time_labels(md)...,) - return TimestepArray{VariableTimestep{TIMES}, T, N}(value) - end -end - -const AnyIndex = Union{Int, Vector{Int}, Tuple, Colon, OrdinalRange} - -# Helper function for getindex; throws a MissingException if data is missing, otherwise returns data -function _missing_data_check(data) - if data === missing - throw(MissingException("Cannot get index; data is missing. You may have tried to access a value that has not yet been computed.")) - else - return data - end -end - -# Helper macro used by connector -macro allow_missing(expr) - let e = gensym("e") - retexpr = quote - try - $expr - catch $e - if $e isa MissingException - missing - else - rethrow($e) - end - end - end - return esc(retexpr) - end -end - -# -# 3b. TimestepVector -# - -function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} - data = v.data[ts.t] - _missing_data_check(data) -end - -function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, ts::VariableTimestep{TIMES}) where {T, TIMES} - data = v.data[ts.t] - _missing_data_check(data) -end - -function Base.getindex(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} - t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) - data = v.data[t] - _missing_data_check(data) -end - -function Base.getindex(v::TimestepVector{VariableTimestep{D_FIRST}, T}, ts::VariableTimestep{T_FIRST}) where {T, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - data = v.data[t] - _missing_data_check(data) -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API - -function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, i::AnyIndex) where {T, FIRST, STEP} - return v.data[i] -end - -function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, i::AnyIndex) where {T, TIMES} - return v.data[i] -end - -function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} - setindex!(v.data, val, ts.t) -end - -function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, ts::VariableTimestep{TIMES}) where {T, TIMES} - setindex!(v.data, val, ts.t) -end - -function Base.setindex!(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} - t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) - setindex!(v.data, val, t) -end - -function Base.setindex!(v::TimestepVector{VariableTimestep{D_FIRST}, T}, val, ts::VariableTimestep{T_FIRST}) where {T, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - setindex!(v.data, val, t) -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API - -function Base.setindex!(v::TimestepVector, val, i::AnyIndex) - setindex!(v.data, val, i) -end - -function Base.length(v::TimestepVector) - return length(v.data) -end - -Base.lastindex(v::TimestepVector) = length(v) - -# -# 3c. TimestepMatrix -# - -function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}, i::AnyIndex) where {T, FIRST, STEP, LAST} - data = mat.data[ts.t, i] - _missing_data_check(data) -end - -function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, ts::VariableTimestep{TIMES}, i::AnyIndex) where {T, TIMES} - data = mat.data[ts.t, i] - _missing_data_check(data) -end - -function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}, i::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} - t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) - data = mat.data[t, i] - _missing_data_check(data) -end - -function Base.getindex(mat::TimestepMatrix{VariableTimestep{D_FIRST}, T}, ts::VariableTimestep{T_FIRST}, i::AnyIndex) where {T, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - data = mat.data[t, i] - _missing_data_check(data) -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API - -function Base.getindex(mat::TimestepMatrix, idx1::AnyIndex, idx2::AnyIndex) - return mat.data[idx1, idx2] -end - -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} - setindex!(mat.data, val, ts.t, idx) -end - -function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, val, ts::VariableTimestep{TIMES}, idx::AnyIndex) where {T, TIMES} - setindex!(mat.data, val, ts.t, idx) -end - -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} - t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) - setindex!(mat.data, val, t, idx) -end - -function Base.setindex!(mat::TimestepMatrix{VariableTimestep{D_FIRST}, T}, val, ts::VariableTimestep{T_FIRST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - setindex!(mat.data, val, t, idx) -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API - -function Base.setindex!(mat::TimestepMatrix, val, idx1::Int, idx2::Int) - setindex!(mat.data, val, idx1, idx2) -end - -function Base.setindex!(mat::TimestepMatrix, val, idx1::AnyIndex, idx2::AnyIndex) - mat.data[idx1,idx2] .= val -end - -# -# 4. TimestepArray methods -# -# Enables broadcast assignment -Base.dotview(v::Mimi.TimestepArray, args...) = Base.dotview(v.data, args...) - -Base.fill!(obj::TimestepArray, value) = fill!(obj.data, value) - -Base.size(obj::TimestepArray) = size(obj.data) - -Base.size(obj::TimestepArray, i::Int) = size(obj.data, i) - -Base.ndims(obj::TimestepArray{T_ts, T, N}) where {T_ts,T, N} = N - -Base.eltype(obj::TimestepArray{T_ts, T, N}) where {T_ts,T, N} = T - -first_period(obj::TimestepArray{FixedTimestep{FIRST,STEP}, T, N}) where {FIRST, STEP, T, N} = FIRST -first_period(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES, T, N} = TIMES[1] - -last_period(obj::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}) where {FIRST, STEP,T, N} = (FIRST + (size(obj, 1) - 1) * STEP) -last_period(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES,T, N} = TIMES[end] - -time_labels(obj::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}) where {FIRST, STEP, T, N} = collect(FIRST:STEP:(FIRST + (size(obj, 1) - 1) * STEP)) -time_labels(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES, T, N} = collect(TIMES) - -function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, ts::FixedTimestep{FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, FIRST, STEP, LAST} - return arr.data[ts.t, idxs...] -end - -function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, ts::VariableTimestep{TIMES}, idxs::AnyIndex...) where {T, N, TIMES} - return arr.data[ts.t, idxs...] -end - -function Base.getindex(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, ts::FixedTimestep{T_FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} - t = Int(ts.t + (FIRST - TIMES[1]) / STEP) - return arr.data[t, idxs...] -end - -function Base.getindex(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, ts::VariableTimestep{T_FIRST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - return arr.data[t, idxs...] -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API; first index is Int or Range, rather than a Timestep - -function Base.getindex(arr::TimestepArray, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) - return arr.data[idx1, idx2, idxs...] -end - -function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, FIRST, STEP, LAST} - setindex!(arr.data, val, ts.t, idxs...) -end - -function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, val, ts::VariableTimestep{TIMES}, idxs::AnyIndex...) where {T, N, TIMES} - setindex!(arr.data, val, ts.t, idxs...) -end - -function Base.setindex!(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - setindex!(arr.data, val, t, idxs...) -end - -function Base.setindex!(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, val, ts::VariableTimestep{T_FIRST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST} - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 - setindex!(arr.data, val, t, idxs...) -end - -# int indexing version supports old-style components and internal functions, not -# part of the public API; first index is Int or Range, rather than a Timestep - -function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, FIRST, STEP} - setindex!(arr.data, val, idx1, idx2, idxs...) -end - -function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, TIMES} - setindex!(arr.data, val, idx1, idx2, idxs...) -end - -""" - hasvalue(arr::TimestepArray, ts::FixedTimestep) - -Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. -""" -function hasvalue(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, N, FIRST, STEP, LAST} - return 1 <= ts.t <= size(arr, 1) -end - -""" - hasvalue(arr::TimestepArray, ts::VariableTimestep) - -Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. -""" -function hasvalue(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, ts::VariableTimestep{TIMES}) where {T, N, TIMES} - return 1 <= ts.t <= size(arr, 1) -end - -function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, N, D_FIRST, T_FIRST, STEP, LAST} - return D_FIRST <= gettime(ts) <= last_period(arr) -end - -function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, ts::VariableTimestep{T_FIRST}) where {T, N, T_FIRST, D_FIRST} - return D_FIRST[1] <= gettime(ts) <= last_period(arr) -end - -""" - hasvalue(arr::TimestepArray, ts::FixedTimestep, idxs::Int...) - -Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within -indices `idxs`. Used when Array and Timestep have different FIRST, validating all dimensions. -""" -function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, - ts::FixedTimestep{T_FIRST, STEP, LAST}, - idxs::Int...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} - return D_FIRST <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) -end - -""" - hasvalue(arr::TimestepArray, ts::VariableTimestep, idxs::Int...) - -Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within -indices `idxs`. Used when Array and Timestep different TIMES, validating all dimensions. -""" -function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, - ts::VariableTimestep{T_FIRST}, - idxs::Int...) where {T, N, D_FIRST, T_FIRST} - - return D_FIRST[1] <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) -end diff --git a/src/core/time_arrays.jl b/src/core/time_arrays.jl new file mode 100644 index 000000000..ac5559396 --- /dev/null +++ b/src/core/time_arrays.jl @@ -0,0 +1,307 @@ +# +# TimestepVector and TimestepMatrix +# + +# +# a. General +# + +# Get a timestep array of type T with N dimensions. Time labels will match those from the time dimension in md +function get_timestep_array(md::ModelDef, T, N, value) + if isuniform(md) + first, stepsize = first_and_step(md) + return TimestepArray{FixedTimestep{first, stepsize}, T, N}(value) + else + TIMES = (time_labels(md)...,) + return TimestepArray{VariableTimestep{TIMES}, T, N}(value) + end +end + +const AnyIndex = Union{Int, Vector{Int}, Tuple, Colon, OrdinalRange} + +# Helper function for getindex; throws a MissingException if data is missing, otherwise returns data +function _missing_data_check(data) + if data === missing + throw(MissingException("Cannot get index; data is missing. You may have tried to access a value that has not yet been computed.")) + else + return data + end +end + +# Helper macro used by connector +macro allow_missing(expr) + let e = gensym("e") + retexpr = quote + try + $expr + catch $e + if $e isa MissingException + missing + else + rethrow($e) + end + end + end + return esc(retexpr) + end +end + +# +# b. TimestepVector +# + +function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} + data = v.data[ts.t] + _missing_data_check(data) +end + +function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, ts::VariableTimestep{TIMES}) where {T, TIMES} + data = v.data[ts.t] + _missing_data_check(data) +end + +function Base.getindex(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} + t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) + data = v.data[t] + _missing_data_check(data) +end + +function Base.getindex(v::TimestepVector{VariableTimestep{D_FIRST}, T}, ts::VariableTimestep{T_FIRST}) where {T, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + data = v.data[t] + _missing_data_check(data) +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API + +function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, i::AnyIndex) where {T, FIRST, STEP} + return v.data[i] +end + +function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, i::AnyIndex) where {T, TIMES} + return v.data[i] +end + +function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} + setindex!(v.data, val, ts.t) +end + +function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, ts::VariableTimestep{TIMES}) where {T, TIMES} + setindex!(v.data, val, ts.t) +end + +function Base.setindex!(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} + t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) + setindex!(v.data, val, t) +end + +function Base.setindex!(v::TimestepVector{VariableTimestep{D_FIRST}, T}, val, ts::VariableTimestep{T_FIRST}) where {T, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + setindex!(v.data, val, t) +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API + +function Base.setindex!(v::TimestepVector, val, i::AnyIndex) + setindex!(v.data, val, i) +end + +function Base.length(v::TimestepVector) + return length(v.data) +end + +Base.lastindex(v::TimestepVector) = length(v) + +# +# c. TimestepMatrix +# + +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}, i::AnyIndex) where {T, FIRST, STEP, LAST} + data = mat.data[ts.t, i] + _missing_data_check(data) +end + +function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, ts::VariableTimestep{TIMES}, i::AnyIndex) where {T, TIMES} + data = mat.data[ts.t, i] + _missing_data_check(data) +end + +function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}, i::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} + t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) + data = mat.data[t, i] + _missing_data_check(data) +end + +function Base.getindex(mat::TimestepMatrix{VariableTimestep{D_FIRST}, T}, ts::VariableTimestep{T_FIRST}, i::AnyIndex) where {T, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + data = mat.data[t, i] + _missing_data_check(data) +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API + +function Base.getindex(mat::TimestepMatrix, idx1::AnyIndex, idx2::AnyIndex) + return mat.data[idx1, idx2] +end + +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} + setindex!(mat.data, val, ts.t, idx) +end + +function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T}, val, ts::VariableTimestep{TIMES}, idx::AnyIndex) where {T, TIMES} + setindex!(mat.data, val, ts.t, idx) +end + +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} + t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) + setindex!(mat.data, val, t, idx) +end + +function Base.setindex!(mat::TimestepMatrix{VariableTimestep{D_FIRST}, T}, val, ts::VariableTimestep{T_FIRST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + setindex!(mat.data, val, t, idx) +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API + +function Base.setindex!(mat::TimestepMatrix, val, idx1::Int, idx2::Int) + setindex!(mat.data, val, idx1, idx2) +end + +function Base.setindex!(mat::TimestepMatrix, val, idx1::AnyIndex, idx2::AnyIndex) + mat.data[idx1,idx2] .= val +end + +# +# TimestepArray methods +# +# Enables broadcast assignment +Base.dotview(v::Mimi.TimestepArray, args...) = Base.dotview(v.data, args...) + +Base.fill!(obj::TimestepArray, value) = fill!(obj.data, value) + +Base.size(obj::TimestepArray) = size(obj.data) + +Base.size(obj::TimestepArray, i::Int) = size(obj.data, i) + +Base.ndims(obj::TimestepArray{T_ts, T, N}) where {T_ts,T, N} = N + +Base.eltype(obj::TimestepArray{T_ts, T, N}) where {T_ts,T, N} = T + +first_period(obj::TimestepArray{FixedTimestep{FIRST,STEP}, T, N}) where {FIRST, STEP, T, N} = FIRST +first_period(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES, T, N} = TIMES[1] + +last_period(obj::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}) where {FIRST, STEP,T, N} = (FIRST + (size(obj, 1) - 1) * STEP) +last_period(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES,T, N} = TIMES[end] + +time_labels(obj::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}) where {FIRST, STEP, T, N} = collect(FIRST:STEP:(FIRST + (size(obj, 1) - 1) * STEP)) +time_labels(obj::TimestepArray{VariableTimestep{TIMES}, T, N}) where {TIMES, T, N} = collect(TIMES) + +function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, ts::FixedTimestep{FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, FIRST, STEP, LAST} + return arr.data[ts.t, idxs...] +end + +function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, ts::VariableTimestep{TIMES}, idxs::AnyIndex...) where {T, N, TIMES} + return arr.data[ts.t, idxs...] +end + +function Base.getindex(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, ts::FixedTimestep{T_FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} + t = Int(ts.t + (FIRST - TIMES[1]) / STEP) + return arr.data[t, idxs...] +end + +function Base.getindex(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, ts::VariableTimestep{T_FIRST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + return arr.data[t, idxs...] +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API; first index is Int or Range, rather than a Timestep + +function Base.getindex(arr::TimestepArray, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) + return arr.data[idx1, idx2, idxs...] +end + +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, FIRST, STEP, LAST} + setindex!(arr.data, val, ts.t, idxs...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, val, ts::VariableTimestep{TIMES}, idxs::AnyIndex...) where {T, N, TIMES} + setindex!(arr.data, val, ts.t, idxs...) +end + +function Base.setindex!(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + setindex!(arr.data, val, t, idxs...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, val, ts::VariableTimestep{T_FIRST}, idxs::AnyIndex...) where {T, N, D_FIRST, T_FIRST} + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + setindex!(arr.data, val, t, idxs...) +end + +# int indexing version supports old-style components and internal functions, not +# part of the public API; first index is Int or Range, rather than a Timestep + +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, FIRST, STEP} + setindex!(arr.data, val, idx1, idx2, idxs...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, TIMES} + setindex!(arr.data, val, idx1, idx2, idxs...) +end + +""" + hasvalue(arr::TimestepArray, ts::FixedTimestep) + +Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. +""" +function hasvalue(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, N, FIRST, STEP, LAST} + return 1 <= ts.t <= size(arr, 1) +end + +""" + hasvalue(arr::TimestepArray, ts::VariableTimestep) + +Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. +""" +function hasvalue(arr::TimestepArray{VariableTimestep{TIMES}, T, N}, ts::VariableTimestep{TIMES}) where {T, N, TIMES} + return 1 <= ts.t <= size(arr, 1) +end + +function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, N, D_FIRST, T_FIRST, STEP, LAST} + return D_FIRST <= gettime(ts) <= last_period(arr) +end + +function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, ts::VariableTimestep{T_FIRST}) where {T, N, T_FIRST, D_FIRST} + return D_FIRST[1] <= gettime(ts) <= last_period(arr) +end + +""" + hasvalue(arr::TimestepArray, ts::FixedTimestep, idxs::Int...) + +Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within +indices `idxs`. Used when Array and Timestep have different FIRST, validating all dimensions. +""" +function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N}, + ts::FixedTimestep{T_FIRST, STEP, LAST}, + idxs::Int...) where {T, N, D_FIRST, T_FIRST, STEP, LAST} + return D_FIRST <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) +end + +""" + hasvalue(arr::TimestepArray, ts::VariableTimestep, idxs::Int...) + +Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within +indices `idxs`. Used when Array and Timestep different TIMES, validating all dimensions. +""" +function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N}, + ts::VariableTimestep{T_FIRST}, + idxs::Int...) where {T, N, D_FIRST, T_FIRST} + + return D_FIRST[1] <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) +end diff --git a/src/core/types.jl b/src/core/types.jl deleted file mode 100644 index da04dae89..000000000 --- a/src/core/types.jl +++ /dev/null @@ -1,717 +0,0 @@ -using Classes -using DataStructures - -""" - @or(args...) - -Return the first argument whose value is not `nothing` -""" -macro or(a, b) - esc(:($a === nothing ? $b : $a)) -end - -# Having all our structs/classes subtype these simplifies "show" methods -abstract type MimiStruct end -@class MimiClass <: Class - -const AbstractMimiType = Union{MimiStruct, AbstractMimiClass} - -# To identify components, @defcomp creates a variable with the name of -# the component whose value is an instance of this type. -struct ComponentId <: MimiStruct - module_name::Symbol - comp_name::Symbol -end - -ComponentId(m::Module, comp_name::Symbol) = ComponentId(nameof(m), comp_name) - -# ComponentPath identifies the path through multiple composites to a leaf comp. -struct ComponentPath <: MimiStruct - names::NTuple{N, Symbol} where N -end - -ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) -ComponentPath(names::Vararg{Symbol}) = ComponentPath(names) - -ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath(path.names..., name) - -ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath(path1.names..., path2.names...) - -ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) - -const ParamPath = Tuple{ComponentPath, Symbol} - -# -# 1. Types supporting parameterized Timestep and Clock objects -# - -abstract type AbstractTimestep <: MimiStruct end - -struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep - t::Int -end - -struct VariableTimestep{TIMES} <: AbstractTimestep - t::Int - current::Int - - function VariableTimestep{TIMES}(t::Int = 1) where {TIMES} - # The special case below handles when functions like next_step step beyond - # the end of the TIMES array. The assumption is that the length of this - # last timestep, starting at TIMES[end], is 1. - current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t] - - return new(t, current) - end -end - -mutable struct Clock{T <: AbstractTimestep} <: MimiStruct - ts::T - - function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T - return new(FixedTimestep{FIRST, STEP, LAST}(1)) - end - - function Clock{T}(TIMES::NTuple{N, Int} where N) where T - return new(VariableTimestep{TIMES}()) - end -end - -mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} <: MimiStruct - data::Array{T, N} - - function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} - return new(d) - end - - function TimestepArray{T_TS, T, N}(lengths::Int...) where {T_TS, T, N} - return new(Array{T, N}(undef, lengths...)) - end -end - -# Since these are the most common cases, we define methods (in time.jl) -# specific to these type aliases, avoiding some of the inefficiencies -# associated with an arbitrary number of dimensions. -const TimestepMatrix{T_TS, T} = TimestepArray{T_TS, T, 2} -const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1} - -# -# 2. Dimensions -# - -abstract type AbstractDimension <: MimiStruct end - -const DimensionKeyTypes = Union{AbstractString, Symbol, Int, Float64} -const DimensionRangeTypes = Union{UnitRange{Int}, StepRange{Int, Int}} - -struct Dimension{T <: DimensionKeyTypes} <: AbstractDimension - dict::OrderedDict{T, Int} - - function Dimension(keys::Vector{T}) where {T <: DimensionKeyTypes} - dict = OrderedDict(collect(zip(keys, 1:length(keys)))) - return new{T}(dict) - end - - function Dimension(rng::T) where {T <: DimensionRangeTypes} - return Dimension(collect(rng)) - end - - Dimension(i::Int) = Dimension(1:i) - - # Support Dimension(:foo, :bar, :baz) - function Dimension(keys::T...) where {T <: DimensionKeyTypes} - vector = [key for key in keys] - return Dimension(vector) - end -end - -# -# Simple optimization for ranges since indices are computable. -# Unclear whether this is really any better than simply using -# a dict for all cases. Might scrap this in the end. -# -mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension - range::T - end - -# -# 3. Types supporting Parameters and their connections -# -abstract type ModelParameter <: MimiStruct end - -# TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? - -mutable struct ScalarModelParameter{T} <: ModelParameter - value::T - - function ScalarModelParameter{T}(value::T) where T - new(value) - end - - function ScalarModelParameter{T1}(value::T2) where {T1, T2} - try - new(T1(value)) - catch err - error("Failed to convert $value::$T2 to $T1") - end - end -end - -mutable struct ArrayModelParameter{T} <: ModelParameter - values::T - dim_names::Vector{Symbol} # if empty, we don't have the dimensions' name information - - function ArrayModelParameter{T}(values::T, dims::Vector{Symbol}) where T - new(values, dims) - end -end - -ScalarModelParameter(value) = ScalarModelParameter{typeof(value)}(value) - -Base.convert(::Type{ScalarModelParameter{T}}, value::Number) where {T} = ScalarModelParameter{T}(T(value)) - -Base.convert(::Type{T}, s::ScalarModelParameter{T}) where {T} = T(s.value) - -ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(value)}(value, dims) - -# Allow values to be obtained from either parameter type using one method name. -value(param::ArrayModelParameter) = param.values -value(param::ScalarModelParameter) = param.value - -dim_names(obj::ArrayModelParameter) = obj.dim_names -dim_names(obj::ScalarModelParameter) = [] - - -abstract type AbstractConnection <: MimiStruct end - -struct InternalParameterConnection <: AbstractConnection - src_comp_path::ComponentPath - src_var_name::Symbol - dst_comp_path::ComponentPath - dst_par_name::Symbol - ignoreunits::Bool - backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing - offset::Int - - function InternalParameterConnection(src_path::ComponentPath, src_var::Symbol, - dst_path::ComponentPath, dst_par::Symbol, - ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) - self = new(src_path, src_var, dst_path, dst_par, ignoreunits, backup, offset) - return self - end -end - -struct ExternalParameterConnection <: AbstractConnection - comp_path::ComponentPath - param_name::Symbol # name of the parameter in the component - external_param::Symbol # name of the parameter stored in external_params -end - -# -# 4. Types supporting structural definition of models and their components -# - -# Objects with a `name` attribute -@class NamedObj <: MimiClass begin - name::Symbol -end - -""" - nameof(obj::NamedDef) = obj.name - -Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, -`CompositeComponentDef`, and `VariableDefReference` and `ParameterDefReference`. -""" -Base.nameof(obj::AbstractNamedObj) = obj.name - -# TBD: if DatumReference refers to the "registered" components, then ComponentId -# is adequate for locating it. As David suggested, having separate types for the -# registered components and the user's ModelDef structure would be clarifying. - -# Similar structure is used for variables and parameters (parameters merely adds `default`) -@class mutable DatumDef <: NamedObj begin - datatype::DataType - dim_names::Vector{Symbol} - description::String - unit::String -end - -@class mutable VariableDef <: DatumDef - -@class mutable ParameterDef <: DatumDef begin - # ParameterDef adds a default value, which can be specified in @defcomp - default::Any -end - -@class mutable ComponentDef <: NamedObj begin - comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) - comp_path::Union{Nothing, ComponentPath} - variables::OrderedDict{Symbol, VariableDef} - parameters::OrderedDict{Symbol, ParameterDef} - dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} - first::Union{Nothing, Int} - last::Union{Nothing, Int} - is_uniform::Bool - - # Store a reference to the AbstractCompositeComponent that contains this comp def. - # That type is defined later, so we declare Any here. - parent::Union{Nothing, Any} - - function ComponentDef(self::ComponentDef, comp_id::Nothing) - error("Leaf ComponentDef objects must have a valid ComponentId name (not nothing)") - end - - # ComponentDefs are created "empty". Elements are subsequently added. - function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; - name::Union{Nothing, Symbol}=nothing) - if comp_id === nothing - # ModelDefs are anonymous, but since they're gensym'd, they can claim the Mimi package - comp_id = ComponentId(Mimi, @or(name, gensym(nameof(typeof(self))))) - end - - name = @or(name, comp_id.comp_name) - NamedObj(self, name) - - self.comp_id = comp_id - self.comp_path = nothing # this is set in add_comp!() and ModelDef() - self.variables = OrderedDict{Symbol, VariableDef}() - self.parameters = OrderedDict{Symbol, ParameterDef}() - self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() - self.first = self.last = nothing - self.is_uniform = true - self.parent = nothing - return self - end - - function ComponentDef(comp_id::Union{Nothing, ComponentId}; - name::Union{Nothing, Symbol}=nothing) - self = new() - return ComponentDef(self, comp_id, name=name) - end -end - -comp_id(obj::AbstractComponentDef) = obj.comp_id -pathof(obj::AbstractComponentDef) = obj.comp_path -dim_dict(obj::AbstractComponentDef) = obj.dim_dict -first_period(obj::AbstractComponentDef) = obj.first -last_period(obj::AbstractComponentDef) = obj.last -isuniform(obj::AbstractComponentDef) = obj.is_uniform - -Base.parent(obj::AbstractComponentDef) = obj.parent - -# Stores references to the name of a component variable or parameter -# and the ComponentPath of the component in which it is defined -@class DatumReference <: NamedObj begin - root::AbstractComponentDef - comp_path::ComponentPath -end - -@class ParameterDefReference <: DatumReference -@class VariableDefReference <: DatumReference - -# Used by @defcomposite to communicate subcomponent information -struct SubComponent <: MimiStruct - module_name::Union{Nothing, Symbol} - comp_name::Symbol - alias::Union{Nothing, Symbol} - exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} - bindings::Vector{Pair{Symbol, Any}} -end - -# Define type aliases to avoid repeating these in several places -global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} -global const ExportsDict = Dict{Symbol, AbstractDatumReference} - -@class mutable CompositeComponentDef <: ComponentDef begin - comps_dict::OrderedDict{Symbol, AbstractComponentDef} - bindings::Vector{Binding} - exports::ExportsDict - - internal_param_conns::Vector{InternalParameterConnection} - external_param_conns::Vector{ExternalParameterConnection} - external_params::Dict{Symbol, ModelParameter} - - # Names of external params that the ConnectorComps will use as their :input2 parameters. - backups::Vector{Symbol} - - sorted_comps::Union{Nothing, Vector{Symbol}} - - function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}=nothing) - self = new() - CompositeComponentDef(self, comp_id) - return self - end - - function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::Union{Nothing, ComponentId}=nothing) - ComponentDef(self, comp_id) # call superclass' initializer - - self.comp_path = ComponentPath(self.name) - self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() - self.bindings = Vector{Binding}() - self.exports = ExportsDict() - self.internal_param_conns = Vector{InternalParameterConnection}() - self.external_param_conns = Vector{ExternalParameterConnection}() - self.external_params = Dict{Symbol, ModelParameter}() - self.backups = Vector{Symbol}() - self.sorted_comps = nothing - end -end - -# Used by @defcomposite -function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Vector{SubComponent}, - calling_module::Module) - # @info "CompositeComponentDef($comp_id, $alias, $subcomps)" - composite = CompositeComponentDef(comp_id) - - for c in subcomps - subcomp_id = ComponentId(@or(c.module_name, calling_module), c.comp_name) - subcomp = compdef(subcomp_id) - - x = printable(subcomp === nothing ? nothing : subcomp_id) - y = printable(composite === nothing ? nothing : comp_id) - # @info "CompositeComponentDef calling add_comp!($y, $x)" - - add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) - end - return composite -end - -# TBD: these should dynamically and recursively compute the lists -internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns -external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns - -# TBD: should only ModelDefs have external params? -external_params(obj::AbstractCompositeComponentDef) = obj.external_params - -exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) -is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) - -add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) - -is_leaf(c::AbstractComponentDef) = true -is_leaf(c::AbstractCompositeComponentDef) = false -is_composite(c::AbstractComponentDef) = !is_leaf(c) - -ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath(obj.comp_path, name) - -@class mutable ModelDef <: CompositeComponentDef begin - number_type::DataType - dirty::Bool - - function ModelDef(number_type::DataType=Float64) - self = new() - CompositeComponentDef(self) # call super's initializer - - # TBD: now set in CompositeComponentDef(self); delete if that works better - # self.comp_path = ComponentPath(self.name) - - return ModelDef(self, number_type, false) # call @class-generated method - end -end - -# -# 5. Types supporting instantiated models and their components -# - -# Supertype for variables and parameters in component instances -@class ComponentInstanceData{NT <: NamedTuple} <: MimiClass begin - nt::NT - comp_paths::Vector{ComponentPath} # records the origin of each datum -end - -nt(obj::AbstractComponentInstanceData) = getfield(obj, :nt) -types(obj::AbstractComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters -Base.names(obj::AbstractComponentInstanceData) = keys(nt(obj)) -Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) - -# Centralizes the shared functionality from the two component data subtypes. -function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, - names, types, values, paths) - # @info "_datum_instance: names=$names, types=$types" - NT = NamedTuple{Tuple(names), Tuple{types...}} - return subtype(NT(values), Vector{ComponentPath}(paths)) -end - -@class ComponentInstanceParameters <: ComponentInstanceData begin - function ComponentInstanceParameters(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} - return new{NT}(nt, paths) - end - - function ComponentInstanceParameters(names::Vector{Symbol}, - types::Vector{DataType}, - values::Vector{Any}, - paths) - return _datum_instance(ComponentInstanceParameters, names, types, values, paths) - end -end - -@class ComponentInstanceVariables <: ComponentInstanceData begin - function ComponentInstanceVariables(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} - return new{NT}(nt, paths) - end - - function ComponentInstanceVariables(names::Vector{Symbol}, - types::Vector{DataType}, - values::Vector{Any}, - paths) - return _datum_instance(ComponentInstanceVariables, names, types, values, paths) - end -end - -# A container class that wraps the dimension dictionary when passed to run_timestep() -# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. -struct DimValueDict <: MimiStruct - dict::Dict{Symbol, Vector{Int}} - - function DimValueDict(dim_dict::AbstractDict) - d = Dict([name => collect(values(dim)) for (name, dim) in dim_dict]) - new(d) - end -end - -# Special case support for Dicts so we can use dot notation on dimension. -# The run_timestep() and init() funcs pass a DimValueDict of dimensions by name -# as the "d" parameter. -Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] - -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: MimiClass begin - comp_name::Symbol - comp_id::ComponentId - comp_path::ComponentPath - variables::TV # TBD: write functions to extract these from type instead of storing? - parameters::TP - first::Union{Nothing, Int} - last::Union{Nothing, Int} - init::Union{Nothing, Function} - run_timestep::Union{Nothing, Function} - - function ComponentInstance(self::AbstractComponentInstance, - comp_def::AbstractComponentDef, - vars::TV, pars::TP, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self.comp_id = comp_id = comp_def.comp_id - self.comp_path = comp_def.comp_path - self.comp_name = name - self.variables = vars - self.parameters = pars - - # If first or last is `nothing`, substitute first or last time period - self.first = @or(comp_def.first, time_bounds[1]) - self.last = @or(comp_def.last, time_bounds[2]) - - # @info "ComponentInstance evaluating $(comp_id.module_name)" - module_name = comp_id.module_name - comp_module = getfield(Main, module_name) - - # The try/catch allows components with no run_timestep function (as in some of our test cases) - # CompositeComponentInstances use a standard method that just loops over inner components. - # TBD: use FunctionWrapper here? - function get_func(name) - if is_composite(self) - return nothing - end - - func_name = Symbol("$(name)_$(nameof(comp_module))_$(self.comp_id.comp_name)") - try - getfield(comp_module, func_name) - catch err - # @info "Eval of $func_name in module $comp_module failed" - nothing - end - end - - # `is_composite` indicates a ComponentInstance used to store summary - # data for ComponentInstance and is not itself runnable. - self.init = get_func("init") - self.run_timestep = get_func("run_timestep") - - return self - end - - # Create an empty instance with the given type parameters - function ComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - return new{TV, TP}() - end -end - -function ComponentInstance(comp_def::AbstractComponentDef, vars::TV, pars::TP, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - - self = ComponentInstance{TV, TP}() - return ComponentInstance(self, comp_def, vars, pars, time_bounds, name) -end - -# These can be called on CompositeComponentInstances and ModelInstances -compdef(obj::AbstractComponentInstance) = compdef(comp_id(obj)) -pathof(obj::AbstractComponentInstance) = obj.comp_path -has_dim(obj::AbstractComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) -dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[name] -first_period(obj::AbstractComponentInstance) = obj.first -last_period(obj::AbstractComponentInstance) = obj.last - -# -# Include only exported vars and pars -# -""" -Return the ComponentInstanceParameters/Variables exported by the given list of -component instances. -""" -function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, - comps::Vector{<: AbstractComponentInstance}) - vdict = Dict([:types => [], :names => [], :values => [], :paths => []]) - pdict = Dict([:types => [], :names => [], :values => [], :paths => []]) - - root = get_root(comp_def) # to find comp_defs by path - - comps_dict = Dict([comp.comp_name => comp for comp in comps]) - - for (export_name, dr) in comp_def.exports - datum_comp = find_comp(dr) - datum_name = nameof(dr) - ci = comps_dict[nameof(datum_comp)] - - datum = (is_parameter(dr) ? ci.parameters : ci.variables) - d = (is_parameter(dr) ? pdict : vdict) - - # Find the position of the desired field in the named tuple - # so we can extract it's datatype. - pos = findfirst(isequal(datum_name), names(datum)) - datatypes = types(datum) - dtype = datatypes[pos] - value = getproperty(datum, datum_name) - - push!(d[:names], export_name) - push!(d[:types], dtype) - push!(d[:values], value) - push!(d[:paths], dr.comp_path) - end - - vars = ComponentInstanceVariables(Vector{Symbol}(vdict[:names]), Vector{DataType}(vdict[:types]), - Vector{Any}(vdict[:values]), Vector{ComponentPath}(vdict[:paths])) - - pars = ComponentInstanceParameters(Vector{Symbol}(pdict[:names]), Vector{DataType}(pdict[:types]), - Vector{Any}(pdict[:values]), Vector{ComponentPath}(pdict[:paths])) - return vars, pars -end - -@class mutable CompositeComponentInstance <: ComponentInstance begin - comps_dict::OrderedDict{Symbol, AbstractComponentInstance} - - function CompositeComponentInstance(self::AbstractCompositeComponentInstance, - comps::Vector{<: AbstractComponentInstance}, - comp_def::AbstractCompositeComponentDef, - vars::ComponentInstanceVariables, - pars::ComponentInstanceParameters, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) - - comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() - for ci in comps - comps_dict[ci.comp_name] = ci - end - - ComponentInstance(self, comp_def, vars, pars, time_bounds, name) - CompositeComponentInstance(self, comps_dict) - return self - end - - # Constructs types of vars and params from sub-components - function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, - comp_def::AbstractCompositeComponentDef, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) - (vars, pars) = _comp_instance_vars_pars(comp_def, comps) - self = new{typeof(vars), typeof(pars)}() - CompositeComponentInstance(self, comps, comp_def, vars, pars, time_bounds, name) - end -end - -# These methods can be called on ModelInstances as well -components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) -has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) -compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] - -is_leaf(ci::AbstractComponentInstance) = true -is_leaf(ci::AbstractCompositeComponentInstance) = false -is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) - -# ModelInstance holds the built model that is ready to be run -@class ModelInstance <: CompositeComponentInstance begin - md::ModelDef - - # Similar to generated constructor, but extract {TV, TP} from argument. - function ModelInstance(cci::CompositeComponentInstance{TV, TP}, md::ModelDef) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - return ModelInstance{TV, TP}(cci, md) - end -end - -# -# 6. User-facing Model types providing a simplified API to model definitions and instances. -# -""" - Model - -A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). -This `Model` can be created with the optional keyword argument `number_type` indicating -the default type of number used for the `ModelDef`. If not specified the `Model` assumes -a `number_type` of `Float64`. -""" -mutable struct Model <: MimiStruct - md::ModelDef - mi::Union{Nothing, ModelInstance} - - function Model(number_type::DataType=Float64) - return new(ModelDef(number_type), nothing) - end - - # Create a copy of a model, e.g., to create marginal models - function Model(m::Model) - return new(deepcopy(m.md), nothing) - end -end - -""" - MarginalModel - -A Mimi `Model` whose results are obtained by subtracting results of one `base` Model -from those of another `marginal` Model` that has a difference of `delta`. -""" -struct MarginalModel <: MimiStruct - base::Model - marginal::Model - delta::Float64 - - function MarginalModel(base::Model, delta::Float64=1.0) - return new(base, Model(base), delta) - end -end - -function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) - return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta -end - -# -# 7. Reference types provide more convenient syntax for interrogating Components -# - -# A container for a component, for interacting with it within a model. -@class ComponentReference <: MimiClass begin - parent::AbstractComponentDef - comp_path::ComponentPath -end - -function ComponentReference(parent::AbstractComponentDef, name::Symbol) - return ComponentReference(parent, ComponentPath(parent.comp_path, name)) -end - -# A container for a variable within a component, to improve connect_param! aesthetics, -# by supporting subscripting notation via getindex & setindex . -@class VariableReference <: ComponentReference begin - var_name::Symbol -end diff --git a/src/core/types/_includes.jl b/src/core/types/_includes.jl new file mode 100644 index 000000000..b4a41910c --- /dev/null +++ b/src/core/types/_includes.jl @@ -0,0 +1,6 @@ +include("core.jl") +include("time.jl") +include("params.jl") +include("defs.jl") +include("instances.jl") +include("model.jl") diff --git a/src/core/types/core.jl b/src/core/types/core.jl new file mode 100644 index 000000000..b5ef36019 --- /dev/null +++ b/src/core/types/core.jl @@ -0,0 +1,95 @@ +using Classes +using DataStructures + +""" + @or(args...) + +Return `a` if a !== nothing, else return `b`. Evaluates each expression +at most once. +""" +macro or(a, b) + # @info "or($a, $b)" + tmp = gensym(:tmp) + expr = quote + $tmp = $a + ($tmp === nothing ? $b : $tmp) + end + esc(expr) +end + +# Having all our structs/classes subtype these simplifies "show" methods +abstract type MimiStruct end +@class MimiClass <: Class + +const AbstractMimiType = Union{MimiStruct, AbstractMimiClass} + +# +# TBD: eliminate module_name in favor of module_path +# +# To identify components, @defcomp creates a variable with the name of +# the component whose value is an instance of this type. +struct ComponentId <: MimiStruct + module_path::Union{Nothing, NTuple{N, Symbol} where N} + module_name::Symbol + comp_name::Symbol +end + +ComponentId(module_name::Symbol, comp_name::Symbol) = ComponentId(nothing, module_name, comp_name) + +ComponentId(m::Module, comp_name::Symbol) = ComponentId(fullname(m), nameof(m), comp_name) + +# ComponentPath identifies the path through multiple composites to a leaf comp. +struct ComponentPath <: MimiStruct + names::NTuple{N, Symbol} where N +end + +ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) +ComponentPath(names::Vararg{Symbol}) = ComponentPath(names) + +ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath(path.names..., name) + +ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath(path1.names..., path2.names...) + +ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) + +const ParamPath = Tuple{ComponentPath, Symbol} + +# +# Dimensions +# + +abstract type AbstractDimension <: MimiStruct end + +const DimensionKeyTypes = Union{AbstractString, Symbol, Int, Float64} +const DimensionRangeTypes = Union{UnitRange{Int}, StepRange{Int, Int}} + +struct Dimension{T <: DimensionKeyTypes} <: AbstractDimension + dict::OrderedDict{T, Int} + + function Dimension(keys::Vector{T}) where {T <: DimensionKeyTypes} + dict = OrderedDict(collect(zip(keys, 1:length(keys)))) + return new{T}(dict) + end + + function Dimension(rng::T) where {T <: DimensionRangeTypes} + return Dimension(collect(rng)) + end + + Dimension(i::Int) = Dimension(1:i) + + # Support Dimension(:foo, :bar, :baz) + function Dimension(keys::T...) where {T <: DimensionKeyTypes} + vector = [key for key in keys] + return Dimension(vector) + end +end + +# +# Simple optimization for ranges since indices are computable. +# Unclear whether this is really any better than simply using +# a dict for all cases. Might scrap this in the end. +# +mutable struct RangeDimension{T <: DimensionRangeTypes} <: AbstractDimension + range::T + end + diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl new file mode 100644 index 000000000..a29878139 --- /dev/null +++ b/src/core/types/defs.jl @@ -0,0 +1,224 @@ +# +# Types supporting structural definition of models and their components +# + +# Objects with a `name` attribute +@class NamedObj <: MimiClass begin + name::Symbol +end + +""" + nameof(obj::NamedDef) = obj.name + +Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, +`CompositeComponentDef`, and `VariableDefReference` and `ParameterDefReference`. +""" +Base.nameof(obj::AbstractNamedObj) = obj.name + +# TBD: if DatumReference refers to the "registered" components, then ComponentId +# is adequate for locating it. As David suggested, having separate types for the +# registered components and the user's ModelDef structure would be clarifying. + +# Similar structure is used for variables and parameters (parameters merely adds `default`) +@class mutable DatumDef <: NamedObj begin + datatype::DataType + dim_names::Vector{Symbol} + description::String + unit::String +end + +@class mutable VariableDef <: DatumDef + +@class mutable ParameterDef <: DatumDef begin + # ParameterDef adds a default value, which can be specified in @defcomp + default::Any +end + +@class mutable ComponentDef <: NamedObj begin + comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) + comp_path::Union{Nothing, ComponentPath} + variables::OrderedDict{Symbol, VariableDef} + parameters::OrderedDict{Symbol, ParameterDef} + dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} + first::Union{Nothing, Int} + last::Union{Nothing, Int} + is_uniform::Bool + + # Store a reference to the AbstractCompositeComponent that contains this comp def. + # That type is defined later, so we declare Any here. + parent::Union{Nothing, Any} + + function ComponentDef(self::ComponentDef, comp_id::Nothing) + error("Leaf ComponentDef objects must have a valid ComponentId name (not nothing)") + end + + # ComponentDefs are created "empty". Elements are subsequently added. + function ComponentDef(self::AbstractComponentDef, comp_id::Union{Nothing, ComponentId}=nothing; + name::Union{Nothing, Symbol}=nothing) + if comp_id === nothing + # ModelDefs are anonymous, but since they're gensym'd, they can claim the Mimi package + comp_id = ComponentId(Mimi, @or(name, gensym(nameof(typeof(self))))) + end + + name = @or(name, comp_id.comp_name) + NamedObj(self, name) + + self.comp_id = comp_id + self.comp_path = nothing # this is set in add_comp!() and ModelDef() + self.variables = OrderedDict{Symbol, VariableDef}() + self.parameters = OrderedDict{Symbol, ParameterDef}() + self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() + self.first = self.last = nothing + self.is_uniform = true + self.parent = nothing + return self + end + + function ComponentDef(comp_id::Union{Nothing, ComponentId}; + name::Union{Nothing, Symbol}=nothing) + self = new() + return ComponentDef(self, comp_id; name=name) + end +end + +comp_id(obj::AbstractComponentDef) = obj.comp_id +pathof(obj::AbstractComponentDef) = obj.comp_path +dim_dict(obj::AbstractComponentDef) = obj.dim_dict +first_period(obj::AbstractComponentDef) = obj.first +last_period(obj::AbstractComponentDef) = obj.last +isuniform(obj::AbstractComponentDef) = obj.is_uniform + +Base.parent(obj::AbstractComponentDef) = obj.parent + +# Stores references to the name of a component variable or parameter +# and the ComponentPath of the component in which it is defined +@class DatumReference <: NamedObj begin + root::AbstractComponentDef + comp_path::ComponentPath +end + +@class ParameterDefReference <: DatumReference +@class VariableDefReference <: DatumReference + +# Used by @defcomposite to communicate subcomponent information +struct SubComponent <: MimiStruct + module_name::Union{Nothing, Symbol} + comp_name::Symbol + alias::Union{Nothing, Symbol} + exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} + bindings::Vector{Pair{Symbol, Any}} +end + +# Define type aliases to avoid repeating these in several places +global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} +global const ExportsDict = Dict{Symbol, AbstractDatumReference} + +@class mutable CompositeComponentDef <: ComponentDef begin + comps_dict::OrderedDict{Symbol, AbstractComponentDef} + bindings::Vector{Binding} + exports::ExportsDict + + internal_param_conns::Vector{InternalParameterConnection} + external_param_conns::Vector{ExternalParameterConnection} + external_params::Dict{Symbol, ModelParameter} + + # Names of external params that the ConnectorComps will use as their :input2 parameters. + backups::Vector{Symbol} + + sorted_comps::Union{Nothing, Vector{Symbol}} + + function CompositeComponentDef(comp_id::Union{Nothing, ComponentId}=nothing) + self = new() + CompositeComponentDef(self, comp_id) + return self + end + + function CompositeComponentDef(self::AbstractCompositeComponentDef, comp_id::Union{Nothing, ComponentId}=nothing) + ComponentDef(self, comp_id) # call superclass' initializer + + self.comp_path = ComponentPath(self.name) + self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() + self.bindings = Vector{Binding}() + self.exports = ExportsDict() + self.internal_param_conns = Vector{InternalParameterConnection}() + self.external_param_conns = Vector{ExternalParameterConnection}() + self.external_params = Dict{Symbol, ModelParameter}() + self.backups = Vector{Symbol}() + self.sorted_comps = nothing + end +end + +# Used by @defcomposite +function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Vector{SubComponent}, + calling_module::Module) + # @info "CompositeComponentDef($comp_id, $alias, $subcomps)" + composite = CompositeComponentDef(comp_id) + + for c in subcomps + # @info "subcomp $c: module_name: $(printable(c.module_name)), calling module: $(nameof(calling_module))" + comp_name = @or(c.module_name, nameof(calling_module)) + subcomp_id = ComponentId(comp_name, c.comp_name) + # @info "subcomp_id: $subcomp_id" + subcomp = compdef(subcomp_id, module_obj=(c.module_name === nothing ? calling_module : nothing)) + + # x = printable(subcomp === nothing ? nothing : subcomp_id) + # y = printable(composite === nothing ? nothing : comp_id) + # @info "CompositeComponentDef calling add_comp!($y, $x)" + + add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) + end + return composite +end + +# TBD: these should dynamically and recursively compute the lists +internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns +external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns + +# TBD: should only ModelDefs have external params? +external_params(obj::AbstractCompositeComponentDef) = obj.external_params + +exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) +is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) + +add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) + +is_leaf(c::AbstractComponentDef) = true +is_leaf(c::AbstractCompositeComponentDef) = false +is_composite(c::AbstractComponentDef) = !is_leaf(c) + +ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath(obj.comp_path, name) + +@class mutable ModelDef <: CompositeComponentDef begin + number_type::DataType + dirty::Bool + + function ModelDef(number_type::DataType=Float64) + self = new() + CompositeComponentDef(self) # call super's initializer + + # TBD: now set in CompositeComponentDef(self); delete if that works better + # self.comp_path = ComponentPath(self.name) + + return ModelDef(self, number_type, false) # call @class-generated method + end +end + +# +# Reference types offer a more convenient syntax for interrogating Components. +# + +# A container for a component, for interacting with it within a model. +@class ComponentReference <: MimiClass begin + parent::AbstractComponentDef + comp_path::ComponentPath +end + +function ComponentReference(parent::AbstractComponentDef, name::Symbol) + return ComponentReference(parent, ComponentPath(parent.comp_path, name)) +end + +# A container for a variable within a component, to improve connect_param! aesthetics, +# by supporting subscripting notation via getindex & setindex . +@class VariableReference <: ComponentReference begin + var_name::Symbol +end diff --git a/src/core/types/instances.jl b/src/core/types/instances.jl new file mode 100644 index 000000000..dce3c7d00 --- /dev/null +++ b/src/core/types/instances.jl @@ -0,0 +1,241 @@ +# +# Types supporting instantiated models and their components +# + +# Supertype for variables and parameters in component instances +@class ComponentInstanceData{NT <: NamedTuple} <: MimiClass begin + nt::NT + comp_paths::Vector{ComponentPath} # records the origin of each datum +end + +nt(obj::AbstractComponentInstanceData) = getfield(obj, :nt) +types(obj::AbstractComponentInstanceData) = typeof(nt(obj)).parameters[2].parameters +Base.names(obj::AbstractComponentInstanceData) = keys(nt(obj)) +Base.values(obj::AbstractComponentInstanceData) = values(nt(obj)) + +# Centralizes the shared functionality from the two component data subtypes. +function _datum_instance(subtype::Type{<: AbstractComponentInstanceData}, + names, types, values, paths) + # @info "_datum_instance: names=$names, types=$types" + NT = NamedTuple{Tuple(names), Tuple{types...}} + return subtype(NT(values), Vector{ComponentPath}(paths)) +end + +@class ComponentInstanceParameters <: ComponentInstanceData begin + function ComponentInstanceParameters(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} + return new{NT}(nt, paths) + end + + function ComponentInstanceParameters(names::Vector{Symbol}, + types::Vector{DataType}, + values::Vector{Any}, + paths) + return _datum_instance(ComponentInstanceParameters, names, types, values, paths) + end +end + +@class ComponentInstanceVariables <: ComponentInstanceData begin + function ComponentInstanceVariables(nt::NT, paths::Vector{ComponentPath}) where {NT <: NamedTuple} + return new{NT}(nt, paths) + end + + function ComponentInstanceVariables(names::Vector{Symbol}, + types::Vector{DataType}, + values::Vector{Any}, + paths) + return _datum_instance(ComponentInstanceVariables, names, types, values, paths) + end +end + +# A container class that wraps the dimension dictionary when passed to run_timestep() +# and init(), so we can safely implement Base.getproperty(), allowing `d.regions` etc. +struct DimValueDict <: MimiStruct + dict::Dict{Symbol, Vector{Int}} + + function DimValueDict(dim_dict::AbstractDict) + d = Dict([name => collect(values(dim)) for (name, dim) in dim_dict]) + new(d) + end +end + +# Special case support for Dicts so we can use dot notation on dimension. +# The run_timestep() and init() funcs pass a DimValueDict of dimensions by name +# as the "d" parameter. +Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] + +@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: MimiClass begin + comp_name::Symbol + comp_id::ComponentId + comp_path::ComponentPath + variables::TV # TBD: write functions to extract these from type instead of storing? + parameters::TP + first::Union{Nothing, Int} + last::Union{Nothing, Int} + init::Union{Nothing, Function} + run_timestep::Union{Nothing, Function} + + function ComponentInstance(self::AbstractComponentInstance, + comp_def::AbstractComponentDef, + vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self.comp_id = comp_id = comp_def.comp_id + self.comp_path = comp_def.comp_path + self.comp_name = name + self.variables = vars + self.parameters = pars + + # If first or last is `nothing`, substitute first or last time period + self.first = @or(comp_def.first, time_bounds[1]) + self.last = @or(comp_def.last, time_bounds[2]) + + # @info "ComponentInstance evaluating $(comp_id.module_name)" + module_name = comp_id.module_name + comp_module = getfield(Main, module_name) + + # The try/catch allows components with no run_timestep function (as in some of our test cases) + # CompositeComponentInstances use a standard method that just loops over inner components. + # TBD: use FunctionWrapper here? + function get_func(name) + if is_composite(self) + return nothing + end + + func_name = Symbol("$(name)_$(nameof(comp_module))_$(self.comp_id.comp_name)") + try + getfield(comp_module, func_name) + catch err + # @info "Eval of $func_name in module $comp_module failed" + nothing + end + end + + # `is_composite` indicates a ComponentInstance used to store summary + # data for ComponentInstance and is not itself runnable. + self.init = get_func("init") + self.run_timestep = get_func("run_timestep") + + return self + end + + # Create an empty instance with the given type parameters + function ComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + return new{TV, TP}() + end +end + +function ComponentInstance(comp_def::AbstractComponentDef, vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + + self = ComponentInstance{TV, TP}() + return ComponentInstance(self, comp_def, vars, pars, time_bounds, name) +end + +# These can be called on CompositeComponentInstances and ModelInstances +compdef(obj::AbstractComponentInstance) = compdef(comp_id(obj)) +pathof(obj::AbstractComponentInstance) = obj.comp_path +has_dim(obj::AbstractComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) +dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[name] +first_period(obj::AbstractComponentInstance) = obj.first +last_period(obj::AbstractComponentInstance) = obj.last + +# +# Include only exported vars and pars +# +""" +Return the ComponentInstanceParameters/Variables exported by the given list of +component instances. +""" +function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, + comps::Vector{<: AbstractComponentInstance}) + vdict = Dict([:types => [], :names => [], :values => [], :paths => []]) + pdict = Dict([:types => [], :names => [], :values => [], :paths => []]) + + root = get_root(comp_def) # to find comp_defs by path + + comps_dict = Dict([comp.comp_name => comp for comp in comps]) + + for (export_name, dr) in comp_def.exports + datum_comp = find_comp(dr) + datum_name = nameof(dr) + ci = comps_dict[nameof(datum_comp)] + + datum = (is_parameter(dr) ? ci.parameters : ci.variables) + d = (is_parameter(dr) ? pdict : vdict) + + # Find the position of the desired field in the named tuple + # so we can extract it's datatype. + pos = findfirst(isequal(datum_name), names(datum)) + datatypes = types(datum) + dtype = datatypes[pos] + value = getproperty(datum, datum_name) + + push!(d[:names], export_name) + push!(d[:types], dtype) + push!(d[:values], value) + push!(d[:paths], dr.comp_path) + end + + vars = ComponentInstanceVariables(Vector{Symbol}(vdict[:names]), Vector{DataType}(vdict[:types]), + Vector{Any}(vdict[:values]), Vector{ComponentPath}(vdict[:paths])) + + pars = ComponentInstanceParameters(Vector{Symbol}(pdict[:names]), Vector{DataType}(pdict[:types]), + Vector{Any}(pdict[:values]), Vector{ComponentPath}(pdict[:paths])) + return vars, pars +end + +@class mutable CompositeComponentInstance <: ComponentInstance begin + comps_dict::OrderedDict{Symbol, AbstractComponentInstance} + + function CompositeComponentInstance(self::AbstractCompositeComponentInstance, + comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractCompositeComponentDef, + vars::ComponentInstanceVariables, + pars::ComponentInstanceParameters, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) + + comps_dict = OrderedDict{Symbol, AbstractComponentInstance}() + for ci in comps + comps_dict[ci.comp_name] = ci + end + + ComponentInstance(self, comp_def, vars, pars, time_bounds, name) + CompositeComponentInstance(self, comps_dict) + return self + end + + # Constructs types of vars and params from sub-components + function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, + comp_def::AbstractCompositeComponentDef, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) + (vars, pars) = _comp_instance_vars_pars(comp_def, comps) + self = new{typeof(vars), typeof(pars)}() + CompositeComponentInstance(self, comps, comp_def, vars, pars, time_bounds, name) + end +end + +# These methods can be called on ModelInstances as well +components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) +has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) +compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] + +is_leaf(ci::AbstractComponentInstance) = true +is_leaf(ci::AbstractCompositeComponentInstance) = false +is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) + +# ModelInstance holds the built model that is ready to be run +@class ModelInstance <: CompositeComponentInstance begin + md::ModelDef + + # Similar to generated constructor, but extract {TV, TP} from argument. + function ModelInstance(cci::CompositeComponentInstance{TV, TP}, md::ModelDef) where + {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + return ModelInstance{TV, TP}(cci, md) + end +end diff --git a/src/core/types/model.jl b/src/core/types/model.jl new file mode 100644 index 000000000..6d371fe86 --- /dev/null +++ b/src/core/types/model.jl @@ -0,0 +1,44 @@ +# +# User-facing Model types providing a simplified API to model definitions and instances. +# +""" + Model + +A user-facing API containing a `ModelInstance` (`mi`) and a `ModelDef` (`md`). +This `Model` can be created with the optional keyword argument `number_type` indicating +the default type of number used for the `ModelDef`. If not specified the `Model` assumes +a `number_type` of `Float64`. +""" +mutable struct Model <: MimiStruct + md::ModelDef + mi::Union{Nothing, ModelInstance} + + function Model(number_type::DataType=Float64) + return new(ModelDef(number_type), nothing) + end + + # Create a copy of a model, e.g., to create marginal models + function Model(m::Model) + return new(deepcopy(m.md), nothing) + end +end + +""" + MarginalModel + +A Mimi `Model` whose results are obtained by subtracting results of one `base` Model +from those of another `marginal` Model` that has a difference of `delta`. +""" +struct MarginalModel <: MimiStruct + base::Model + marginal::Model + delta::Float64 + + function MarginalModel(base::Model, delta::Float64=1.0) + return new(base, Model(base), delta) + end +end + +function Base.getindex(mm::MarginalModel, comp_name::Symbol, name::Symbol) + return (mm.marginal[comp_name, name] .- mm.base[comp_name, name]) ./ mm.delta +end diff --git a/src/core/types/params.jl b/src/core/types/params.jl new file mode 100644 index 000000000..9f718d863 --- /dev/null +++ b/src/core/types/params.jl @@ -0,0 +1,73 @@ +# +# Types supporting Parameters and their connections +# + +abstract type ModelParameter <: MimiStruct end + +# TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? + +mutable struct ScalarModelParameter{T} <: ModelParameter + value::T + + function ScalarModelParameter{T}(value::T) where T + new(value) + end + + function ScalarModelParameter{T1}(value::T2) where {T1, T2} + try + new(T1(value)) + catch err + error("Failed to convert $value::$T2 to $T1") + end + end +end + +mutable struct ArrayModelParameter{T} <: ModelParameter + values::T + dim_names::Vector{Symbol} # if empty, we don't have the dimensions' name information + + function ArrayModelParameter{T}(values::T, dims::Vector{Symbol}) where T + new(values, dims) + end +end + +ScalarModelParameter(value) = ScalarModelParameter{typeof(value)}(value) + +Base.convert(::Type{ScalarModelParameter{T}}, value::Number) where {T} = ScalarModelParameter{T}(T(value)) + +Base.convert(::Type{T}, s::ScalarModelParameter{T}) where {T} = T(s.value) + +ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(value)}(value, dims) + +# Allow values to be obtained from either parameter type using one method name. +value(param::ArrayModelParameter) = param.values +value(param::ScalarModelParameter) = param.value + +dim_names(obj::ArrayModelParameter) = obj.dim_names +dim_names(obj::ScalarModelParameter) = [] + + +abstract type AbstractConnection <: MimiStruct end + +struct InternalParameterConnection <: AbstractConnection + src_comp_path::ComponentPath + src_var_name::Symbol + dst_comp_path::ComponentPath + dst_par_name::Symbol + ignoreunits::Bool + backup::Union{Symbol, Nothing} # a Symbol identifying the external param providing backup data, or nothing + offset::Int + + function InternalParameterConnection(src_path::ComponentPath, src_var::Symbol, + dst_path::ComponentPath, dst_par::Symbol, + ignoreunits::Bool, backup::Union{Symbol, Nothing}=nothing; offset::Int=0) + self = new(src_path, src_var, dst_path, dst_par, ignoreunits, backup, offset) + return self + end +end + +struct ExternalParameterConnection <: AbstractConnection + comp_path::ComponentPath + param_name::Symbol # name of the parameter in the component + external_param::Symbol # name of the parameter stored in external_params +end diff --git a/src/core/types/time.jl b/src/core/types/time.jl new file mode 100644 index 000000000..46935b2d6 --- /dev/null +++ b/src/core/types/time.jl @@ -0,0 +1,53 @@ +# +# Types supporting parameterized Timestep and Clock objects +# + +abstract type AbstractTimestep <: MimiStruct end + +struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep + t::Int +end + +struct VariableTimestep{TIMES} <: AbstractTimestep + t::Int + current::Int + + function VariableTimestep{TIMES}(t::Int = 1) where {TIMES} + # The special case below handles when functions like next_step step beyond + # the end of the TIMES array. The assumption is that the length of this + # last timestep, starting at TIMES[end], is 1. + current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t] + + return new(t, current) + end +end + +mutable struct Clock{T <: AbstractTimestep} <: MimiStruct + ts::T + + function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T + return new(FixedTimestep{FIRST, STEP, LAST}(1)) + end + + function Clock{T}(TIMES::NTuple{N, Int} where N) where T + return new(VariableTimestep{TIMES}()) + end +end + +mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N} <: MimiStruct + data::Array{T, N} + + function TimestepArray{T_TS, T, N}(d::Array{T, N}) where {T_TS, T, N} + return new(d) + end + + function TimestepArray{T_TS, T, N}(lengths::Int...) where {T_TS, T, N} + return new(Array{T, N}(undef, lengths...)) + end +end + +# Since these are the most common cases, we define methods (in time.jl) +# specific to these type aliases, avoiding some of the inefficiencies +# associated with an arbitrary number of dimensions. +const TimestepMatrix{T_TS, T} = TimestepArray{T_TS, T, 2} +const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1} From 79719e4ffa286bedd16130cda46c415c70faa0c3 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 17 May 2019 10:14:47 -0700 Subject: [PATCH 57/81] WIP - debugging composite-based MimiDice2013 --- src/core/build.jl | 3 +++ src/core/connections.jl | 9 +++++---- src/core/defs.jl | 37 +++++++++++++++++++++++-------------- src/core/time_arrays.jl | 3 ++- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 60129e8a3..8397a09ea 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -15,6 +15,7 @@ function _instance_datatype(md::ModelDef, def::AbstractDatumDef) else if isuniform(md) first, stepsize = first_and_step(md) + first === nothing && @warn "_instance_datatype: first === nothing" T = TimestepArray{FixedTimestep{first, stepsize}, Union{dtype, Missing}, num_dims} else times = time_labels(md) @@ -240,6 +241,8 @@ function _build(md::ModelDef) t = dimension(md, :time) time_bounds = (firstindex(t), lastindex(t)) + propagate_time(md, t) + ci = _build(md, var_dict, par_dict, time_bounds) mi = ModelInstance(ci, md) return mi diff --git a/src/core/connections.jl b/src/core/connections.jl index c7bfa75a4..be4bf30f0 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -140,8 +140,8 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_comp_def = compdef(obj, dst_comp_path) src_comp_def = compdef(obj, src_comp_path) - @info "src_comp_def calling compdef($(obj.comp_id), $src_comp_path)" - src_comp_def === nothing && @info "src_comp_def === nothing" + # @info "src_comp_def calling compdef($(obj.comp_id), $src_comp_path)" + # src_comp_def === nothing && @info "src_comp_def === nothing" if backup !== nothing # If value is a NamedArray, we can check if the labels match @@ -162,8 +162,9 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_dims = dim_names(dst_param) backup = convert(Array{Union{Missing, number_type(obj)}}, backup) # converts number type and, if it's a NamedArray, it's converted to Array - first = first_period(obj, dst_comp_def) - T = eltype(backup) + first = first_period(obj, dst_comp_def) + + T = eltype(backup) dim_count = length(dst_dims) diff --git a/src/core/defs.jl b/src/core/defs.jl index a44982d04..5dc2f3883 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -314,12 +314,15 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: # @warn "Redefining dimension :$name" # end + dim = Dimension(keys) + if name == :time _set_run_period!(ccd, keys[1], keys[end]) + propagate_time(ccd, dim) set_uniform!(ccd, isuniform(keys)) end - return set_dimension!(ccd, name, Dimension(keys)) + return set_dimension!(ccd, name, dim) end function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) @@ -557,6 +560,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param else # Use the first from the comp_def if it has it, else use the tree root (usu. a ModelDef) first = first_period(obj, comp_def) + first === nothing && @warn "set_param!: first === nothing" if isuniform(obj) stepsize = step_size(obj) @@ -804,23 +808,12 @@ function time_contains(outer::Dimension, inner::Dimension) return outer_idx[1] <= inner_idx[1] && outer_idx[end] >= inner_idx[end] end -""" -Propagate a time dimension down through the comp def tree. -""" -function _propagate_time(obj::AbstractComponentDef, t::Dimension) - set_dimension!(obj, :time, t) - - for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes - _propagate_time(c, t) - end -end - function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol, datum_name::Symbol) path = ComponentPath(parent.comp_path, comp_name) root = get_root(parent) - root === nothing && error("Component $(parent.comp_id) does have a root") + root === nothing && error("Component $(parent.comp_id) does not have a root") # @info "comp path: $path, datum_name: $datum_name" @@ -838,6 +831,22 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract error("Component $(comp_def.comp_id) does not have a data item named $datum_name") end +""" + propagate_time(obj::AbstractComponentDef, t::Dimension) + +Propagate a time dimension down through the comp def tree. +""" +function propagate_time(obj::AbstractComponentDef, t::Dimension) + set_dimension!(obj, :time, t) + + obj.first = firstindex(t) + obj.last = lastindex(t) + + for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes + propagate_time(c, t) + end +end + """ add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; @@ -920,7 +929,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot specify both 'before' and 'after' parameters") end - _propagate_time(comp_def, dimension(obj, :time)) + propagate_time(comp_def, dimension(obj, :time)) end # Copy the original so we don't step on other uses of this comp diff --git a/src/core/time_arrays.jl b/src/core/time_arrays.jl index ac5559396..bc3a6dd16 100644 --- a/src/core/time_arrays.jl +++ b/src/core/time_arrays.jl @@ -9,7 +9,8 @@ # Get a timestep array of type T with N dimensions. Time labels will match those from the time dimension in md function get_timestep_array(md::ModelDef, T, N, value) if isuniform(md) - first, stepsize = first_and_step(md) + first, stepsize = first_and_step(md) + first === nothing && @warn "get_timestep_array: first === nothing" return TimestepArray{FixedTimestep{first, stepsize}, T, N}(value) else TIMES = (time_labels(md)...,) From 7c35f778b9227ca6ad9853721e934812b4d55581 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sun, 26 May 2019 11:08:19 -0700 Subject: [PATCH 58/81] WIP - Debugging propagation of vars/params through composites --- src/core/defs.jl | 137 ++++++++++++++++++++++------------------- src/core/paths.jl | 18 +++++- src/core/show.jl | 6 +- src/core/types/defs.jl | 7 ++- test/test_composite.jl | 56 +++++++++++++---- 5 files changed, 140 insertions(+), 84 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index fb4ca388b..08b0eee39 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -23,10 +23,13 @@ end compdef(cr::ComponentReference) = find_comp(cr) +compdef(dr::AbstractDatumReference) = find_comp(dr.root, dr.comp_path) + compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.comps_dict[comp_name] + has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) @@ -46,14 +49,6 @@ compnames() = map(compname, compdefs()) # Access a subcomponent as comp[:name] Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict[name] -# TBD: deprecated -function reset_compdefs(reload_builtins=true) - if reload_builtins - compdir = joinpath(@__DIR__, "..", "components") - load_comps(compdir) - end -end - """ is_detached(obj::AbstractComponentDef) @@ -86,12 +81,6 @@ is_parameter(dr::AbstractDatumReference) = false is_variable(dr::VariableDefReference) = has_variable(find_comp(dr), nameof(dr)) is_parameter(dr::ParameterDefReference) = has_parameter(find_comp(dr), nameof(dr)) -""" -Return the name of `def`. Possible `NamedDef`s include `DatumDef`, and `ComponentDef`. -""" -name(def::NamedDef) = def.name # old definition; should deprecate this... -Base.nameof(def::NamedDef) = def.name # 'nameof' is the more julian name - number_type(md::ModelDef) = md.number_type function number_type(obj::AbstractCompositeComponentDef) @@ -364,6 +353,39 @@ function isuniform(values::Int) return true end +# +# Data references +# + +function _store_datum_ref(ccd::AbstractCompositeComponentDef, dr::ParameterDefReference, name::Symbol) + ccd.parameters[name] = parameter(dr) +end + +function _store_datum_ref(ccd::AbstractCompositeComponentDef, dr::VariableDefReference, name::Symbol) + ccd.variables[name] = variable(dr) +end + +# Define this no-op for leaf components, to simplify coding +_collect_data_refs(cd::ComponentDef; reset::Bool=false) = nothing + +function _collect_data_refs(ccd::AbstractCompositeComponentDef; reset::Bool=false) + if reset + empty!(ccd.variables) + empty!(ccd.parameters) + end + + for (name, dr) in ccd.exports + _store_datum_ref(ccd, dr, name) + end + + # recurse down composite tree + for obj in compdefs(ccd) + _collect_data_refs(obj, reset=reset) + end + + nothing +end + # # Parameters # @@ -372,7 +394,7 @@ end dim_names(obj::AbstractDatumDef) = obj.dim_names function addparameter(comp_def::AbstractComponentDef, name, datatype, dimensions, description, unit, default) - p = ParameterDef(name, datatype, dimensions, description, unit, default) + p = ParameterDef(name, comp_def.comp_path, datatype, dimensions, description, unit, default) comp_def.parameters[name] = p dirty!(comp_def) return p @@ -389,24 +411,13 @@ Return a list of the parameter definitions for `comp_def`. """ parameters(obj::AbstractComponentDef) = values(obj.parameters) -function parameters(ccd::AbstractCompositeComponentDef) +function parameters(ccd::AbstractCompositeComponentDef; reset::Bool=false) pars = ccd.parameters - - # return cached parameters, if any - if length(pars) == 0 - for (name, dr) in ccd.exports - cd = find_comp(dr) - - if cd === nothing - @info "find_comp failed on path: $(printable(dr.comp_path)), name: $(printable(dr.name)), root: $(printable(dr.root.comp_id))" - end - - if has_parameter(cd, nameof(dr)) - pars[name] = parameter(cd, nameof(dr)) - end - end + + if reset || (ccd isa ModelDef && dirty(ccd)) || length(pars) == 0 + _collect_data_refs(ccd; reset=reset) end - + return values(pars) end @@ -417,9 +428,6 @@ Return a list of the parameter definitions for `comp_id`. """ parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) -# TBD: deprecated? -# parameters(obj::ParameterDefReference) = parameters(obj.comp_id) - """ parameter_names(md::ModelDef, comp_name::Symbol) @@ -442,9 +450,7 @@ function _parameter(obj::AbstractComponentDef, name::Symbol) end end -function parameter(obj::ComponentDef, name::Symbol) - _parameter(obj, name) -end +parameter(obj::ComponentDef, name::Symbol) = _parameter(obj, name) function parameter(obj::AbstractCompositeComponentDef, name::Symbol) if ! is_exported(obj, name) @@ -599,20 +605,17 @@ end # # Variables # + +# Leaf components variables(comp_def::AbstractComponentDef) = values(comp_def.variables) +# Composite components # TBD: if we maintain vars/pars dynamically, this can be dropped -function variables(ccd::AbstractCompositeComponentDef) +function variables(ccd::AbstractCompositeComponentDef; reset::Bool=false) vars = ccd.variables - # return cached variables, if any - if length(vars) == 0 - for (dr, name) in ccd.exports - cd = compdef(dr.comp_id) - if has_variable(cd, nameof(dr)) - vars[name] = variable(cd, nameof(dr)) - end - end + if reset || (ccd isa ModelDef && dirty(ccd)) || length(vars) == 0 + _collect_data_refs(ccd; reset=reset) end return values(vars) @@ -620,22 +623,24 @@ end variables(comp_id::ComponentId) = variables(compdef(comp_id)) -# TBD: Not sure this makes sense -# variables(dr::DatumReference) = variables(dr.comp_id) - -# TBD: Perhaps define _variable to behave as below, and have the public version -# check it's exported before returning it. (Could error("exists but not exported?")) -function variable(comp_def::AbstractComponentDef, var_name::Symbol) - # TBD test this can be dropped if we maintain vars/pars dynamically - if is_composite(comp_def) - variables(comp_def) # make sure values have been gathered +function _variable(obj::AbstractComponentDef, name::Symbol) + try + return obj.variables[name] + catch + error("Variable $name was not found in component $(nameof(obj))") end +end - try - return comp_def.variables[var_name] - catch KeyError - error("Variable $var_name was not found in component $(comp_def.comp_id)") +variable(obj::ComponentDef, name::Symbol) = _variable(obj, name) + +function variable(obj::AbstractCompositeComponentDef, name::Symbol) + # TBD test this can be dropped if we maintain vars/pars dynamically + _collect_data_refs(obj) + + if ! is_exported(obj, name) + error("Variable $name is not exported by composite component $(obj.comp_path)") end + _variable(obj, name) end variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), var_name) @@ -647,7 +652,7 @@ function variable(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, return variable(comp_def, var_name) end -variable(obj::VariableDefReference) = variable(compdef(obj), nameof(dr)) +variable(dr::VariableDefReference) = variable(compdef(dr), nameof(dr)) has_variable(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.variables, name) @@ -685,11 +690,14 @@ end # Add a variable to a ComponentDef. CompositeComponents have no vars of their own, # only references to vars in components contained within. function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) - var_def = VariableDef(name, datatype, dimensions, description, unit) + var_def = VariableDef(name, comp_def.comp_path, datatype, dimensions, description, unit) comp_def.variables[name] = var_def return var_def end +# +# TBD: this is clearly not used because there is no "variable" (other than the func) defined here +# """ addvariables(obj::CompositeComponentDef, exports::Vector{Pair{AbstractDatumReference, Symbol}}) @@ -824,8 +832,11 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract # @info "comp path: $path, datum_name: $datum_name" - # for composites, check that the named vars/pars are exported? - # if is_composite(comp_def) + if is_composite(comp_def) + # find and cache locally exported vars & pars + variables(comp_def) + parameters(comp_def) + end if has_variable(comp_def, datum_name) return VariableDefReference(datum_name, root, path) diff --git a/src/core/paths.jl b/src/core/paths.jl index 06193b76f..d379c086b 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -17,12 +17,24 @@ function Base.string(path::ComponentPath) return is_abspath(path) ? string("/", s) : s end +""" + comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) + +Set the ComponentPath of a child object to extend the path of its composite parent. +For leaf components, also sets the ComponentPath in all ParameterDefs and VariableDefs. +""" function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) - child.comp_path = ComponentPath(parent.comp_path, child.name) + child.comp_path = path = ComponentPath(parent.comp_path, child.name) # recursively reset all comp_paths - for cd in compdefs(child) - comp_path!(cd, child) + if is_composite(child) + for cd in compdefs(child) + comp_path!(cd, child) + end + else + for datum in [variables(child)..., parameters(child)...] + datum.comp_path = path + end end end diff --git a/src/core/show.jl b/src/core/show.jl index 8a3376b23..0ac13d7ca 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -106,7 +106,7 @@ end function _show_datum_def(io::IO, obj::AbstractDatumDef) print(io, typeof(obj), "($(obj.name)::$(obj.datatype))") io = indent(io) - + _show_field(io, :comp_path, obj.comp_path) _show_field(io, :dim_names, obj.dim_names) for field in (:description, :unit) @@ -197,10 +197,10 @@ function _show(io::IO, obj::Model, which::Symbol) md = obj.md mi = obj.mi - println(io, " Module: $(md.module_name)") + println(io, " Module: $(md.comp_id.module_path)") println(io, " Components:") - for comp in values(md.comp_defs) + for comp in values(md.comps_dict) println(io, " $(comp.comp_id)") end diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index a29878139..675c2f69f 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -15,12 +15,12 @@ Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, """ Base.nameof(obj::AbstractNamedObj) = obj.name -# TBD: if DatumReference refers to the "registered" components, then ComponentId -# is adequate for locating it. As David suggested, having separate types for the -# registered components and the user's ModelDef structure would be clarifying. +# TBD: old definition; should deprecate this... +name(obj::AbstractNamedObj) = obj.name # Similar structure is used for variables and parameters (parameters merely adds `default`) @class mutable DatumDef <: NamedObj begin + comp_path::Union{Nothing, ComponentPath} datatype::DataType dim_names::Vector{Symbol} description::String @@ -93,6 +93,7 @@ Base.parent(obj::AbstractComponentDef) = obj.parent # Stores references to the name of a component variable or parameter # and the ComponentPath of the component in which it is defined @class DatumReference <: NamedObj begin + # name::Symbol is inherited from NamedObj root::AbstractComponentDef comp_path::ComponentPath end diff --git a/test/test_composite.jl b/test/test_composite.jl index 86ede80f3..89b480112 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -42,16 +42,43 @@ end end end +@defcomp Comp4 begin + par_4_1 = Parameter(index=[time]) # connected to Comp2.var_2_1 + var_41 = Variable(index=[time]) # external output + foo = Parameter(default=300) + + function run_timestep(p, v, d, t) + # @info "Comp4 run_timestep" + v.var_4_1[t] = p.par_4_1[t] * 2 + end +end + m = Model() set_dimension!(m, :time, 2005:2020) -@defcomposite top begin +@defcomposite A begin component(Comp1; exports=[foo => foo1]) component(Comp2, exports=[foo => foo2]) - component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) - # exports(list of names or pairs to export) end +# @defcomposite B begin +# component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) +# component(Comp4, exports=[foo => foo4]) +# end + +@defcomposite top begin + component(A; exports=[foo1 => fooA1, foo2 => fooA2]) + # component(B; exports=[foo3, foo4]) +end + +# We have created the following composite structure: +# +# top +# / \ +# A B +# / \ / \ +# 1 2 3 4 + top_ref = add_comp!(m, top, nameof(top)) top_comp = compdef(top_ref) @@ -59,20 +86,25 @@ md = m.md @test find_comp(md, :top) == top_comp -c1 = find_comp(md, ComponentPath(:top, :Comp1)) +c1 = find_comp(md, ComponentPath(:top, :A, :Comp1)) @test c1.comp_id == Comp1.comp_id -c3 = find_comp(md, "/top/Comp3") -@test c3.comp_id == Comp3.comp_id +# c3 = find_comp(md, "/top/B/Comp3") +# @test c3.comp_id == Comp3.comp_id + +set_param!(m, "/top/A/Comp1", :foo, 10) +set_param!(m, "/top/A/Comp2", :foo, 20) -set_param!(m, "/top/Comp1", :foo, 10) -set_param!(m, "/top/Comp2", :foo, 20) +set_param!(m, "/top/A/Comp1", :par_1_1, zeros(length(time_labels(md)))) -set_param!(m, "/top/Comp1", :par_1_1, zeros(length(time_labels(md)))) +c1_path = ComponentPath(:A, :Comp1) +c2_path = ComponentPath(:A, :Comp2) +# c3_path = ComponentPath(:B, :Comp3) +# c4_path = ComponentPath(:B, :Comp4) -connect_param!(top, :Comp2, :par_2_1, :Comp1, :var_1_1) -connect_param!(top, :Comp2, :par_2_2, :Comp1, :var_1_1) -connect_param!(top, :Comp3, :par_3_1, :Comp2, :var_2_1) +connect_param!(top, c2_path, :par_2_1, c1_path, :var_1_1) +connect_param!(top, c2_path, :par_2_2, c1_path, :var_1_1) +# connect_param!(top, c3_path, :par_3_1, c2_path, :var_2_1) # build(m) # run(m) From 0a554a1bf0f0d0b35bc8e7afca84d30afbc4f129 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 4 Jun 2019 13:18:31 -0700 Subject: [PATCH 59/81] Removed calls to deprecated reset_compdefs(). --- test/mcs/test_defmcs.jl | 3 +- test/mcs/test_defmcs_sobol.jl | 1 - test/mcs/test_reshaping.jl | 4 +- test/test_adder.jl | 5 -- test/test_clock.jl | 4 +- test/test_components.jl | 2 +- test/test_components_ordering.jl | 2 - test/test_composite.jl | 54 +++++++++++++------ test/test_explorer.jl | 4 +- test/test_getdataframe.jl | 5 -- test/test_getindex.jl | 5 -- test/test_getindex_variabletimestep.jl | 5 -- test/test_main.jl | 2 +- test/test_main_variabletimestep.jl | 2 +- test/test_marginal_models.jl | 5 -- test/test_metainfo.jl | 5 +- test/test_model_structure_variabletimestep.jl | 4 +- test/test_mult_getdataframe.jl | 5 -- test/test_parameter_labels.jl | 5 -- test/test_parametertypes.jl | 7 --- test/test_plotting.jl | 5 -- test/test_references.jl | 4 +- test/test_replace_comp.jl | 5 +- test/test_show.jl | 4 +- test/test_timesteparrays.jl | 4 +- test/test_tmp.jl | 4 +- test/test_tools.jl | 4 +- test/test_units.jl | 4 +- test/test_variables_model_instance.jl | 4 +- 29 files changed, 57 insertions(+), 110 deletions(-) diff --git a/test/mcs/test_defmcs.jl b/test/mcs/test_defmcs.jl index dde85103b..b29beb6f0 100644 --- a/test/mcs/test_defmcs.jl +++ b/test/mcs/test_defmcs.jl @@ -8,8 +8,7 @@ using CSVFiles using Test -using Mimi: reset_compdefs, modelinstance, compinstance, - get_var_value, OUTER, INNER, ReshapedDistribution +using Mimi: modelinstance, compinstance, get_var_value, OUTER, INNER, ReshapedDistribution include("../../examples/tutorial/02-two-region-model/two-region-model.jl") using .MyModel diff --git a/test/mcs/test_defmcs_sobol.jl b/test/mcs/test_defmcs_sobol.jl index a3c6b7a5b..73c7add60 100644 --- a/test/mcs/test_defmcs_sobol.jl +++ b/test/mcs/test_defmcs_sobol.jl @@ -38,7 +38,6 @@ sim = @defsim begin save(grosseconomy.K, grosseconomy.YGROSS, emissions.E, emissions.E_Global) end -Mimi.reset_compdefs() include("../../examples/tutorial/02-two-region-model/main.jl") m = model diff --git a/test/mcs/test_reshaping.jl b/test/mcs/test_reshaping.jl index 6f3db7896..dbf21209a 100644 --- a/test/mcs/test_reshaping.jl +++ b/test/mcs/test_reshaping.jl @@ -7,10 +7,8 @@ using DelimitedFiles using Test -using Mimi: reset_compdefs, modelinstance, compinstance, - get_var_value, OUTER, INNER, ReshapedDistribution +using Mimi: modelinstance, compinstance, get_var_value, OUTER, INNER, ReshapedDistribution -reset_compdefs() include("test-model/test-model.jl") using .TestModel m = create_model() diff --git a/test/test_adder.jl b/test/test_adder.jl index 03c80cdc3..1744e5977 100644 --- a/test/test_adder.jl +++ b/test/test_adder.jl @@ -3,11 +3,6 @@ module TestAdder using Mimi using Test -import Mimi: - reset_compdefs - -reset_compdefs() - ############################################ # adder component without a different name # ############################################ diff --git a/test/test_clock.jl b/test/test_clock.jl index dc0092c73..bfe7b2aff 100644 --- a/test/test_clock.jl +++ b/test/test_clock.jl @@ -5,9 +5,7 @@ using Test import Mimi: AbstractTimestep, FixedTimestep, VariableTimestep, Clock, timestep, time_index, - advance, reset_compdefs - -reset_compdefs() + advance t_f = FixedTimestep{1850, 10, 3000}(1) c_f = Clock{FixedTimestep}(1850, 10, 3000) diff --git a/test/test_components.jl b/test/test_components.jl index b378f440c..b5e81a5c4 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -4,7 +4,7 @@ using Mimi using Test import Mimi: - reset_compdefs, compdefs, compdef, compkeys, has_comp, first_period, + compdefs, compdef, compkeys, has_comp, first_period, last_period, compmodule, compname, numcomponents, compinstance, dim_keys, dim_values my_model = Model() diff --git a/test/test_components_ordering.jl b/test/test_components_ordering.jl index a2e7f4888..95e055f6a 100644 --- a/test/test_components_ordering.jl +++ b/test/test_components_ordering.jl @@ -1,8 +1,6 @@ using Mimi using Test -reset_compdefs() - my_model = Model() #Testing that you cannot add two components of the same name diff --git a/test/test_composite.jl b/test/test_composite.jl index 89b480112..33ac7c296 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -14,7 +14,6 @@ import Mimi: foo = Parameter() function run_timestep(p, v, d, t) - # @info "Comp1 run_timestep" v.var_1_1[t] = p.par_1_1[t] end end @@ -26,8 +25,7 @@ end foo = Parameter() function run_timestep(p, v, d, t) - # @info "Comp2 run_timestep" - v.var_2_1[t] = p.par_2_1[t] + p.par_2_2[t] + v.var_2_1[t] = p.par_2_1[t] + p.foo * p.par_2_2[t] end end @@ -92,23 +90,49 @@ c1 = find_comp(md, ComponentPath(:top, :A, :Comp1)) # c3 = find_comp(md, "/top/B/Comp3") # @test c3.comp_id == Comp3.comp_id -set_param!(m, "/top/A/Comp1", :foo, 10) -set_param!(m, "/top/A/Comp2", :foo, 20) +set_param!(m, "/top/A/Comp1", :foo, 1) +set_param!(m, "/top/A/Comp2", :foo, 2) -set_param!(m, "/top/A/Comp1", :par_1_1, zeros(length(time_labels(md)))) +set_param!(m, "/top/A/Comp1", :par_1_1, collect(1:length(time_labels(md)))) -c1_path = ComponentPath(:A, :Comp1) -c2_path = ComponentPath(:A, :Comp2) -# c3_path = ComponentPath(:B, :Comp3) -# c4_path = ComponentPath(:B, :Comp4) +c1_path = ComponentPath(md, :top, :A, :Comp1) +c2_path = ComponentPath(md, "/top/A/Comp2") +# c3_path = ComponentPath(md, "/top/B/Comp3") +# c4_path = ComponentPath(md, "top/B/Comp4") + +connect_param!(md, c2_path, :par_2_1, c1_path, :var_1_1) +connect_param!(top_comp, c2_path, :par_2_2, c1_path, :var_1_1) +# connect_param!(top_comp, c3_path, :par_3_1, c2_path, :var_2_1) + +build(m) +run(m) + +# +# TBD +# +# 1. Create parallel structure of exported vars/pars in Instance hierarchy +# - Perhaps just a dict mapping local name to a component path under mi, to where the var actually exists +# 2. Be able to connect to the leaf version of vars/pars or by specifying exported version below the compdef +# given as first arg to connect_param!(). +# 3. set_param!() should work with relative path from any compdef. +# 4. get_index should work for any comp/var/par below the given compinstance +# - also support indexing by a path or tuple indicating a path below the given instance -connect_param!(top, c2_path, :par_2_1, c1_path, :var_1_1) -connect_param!(top, c2_path, :par_2_2, c1_path, :var_1_1) -# connect_param!(top, c3_path, :par_3_1, c2_path, :var_2_1) -# build(m) -# run(m) end # module +m = TestComposite.m +md = m.md +top = Mimi.find_comp(md, :top) +A = Mimi.find_comp(top, :A) +comp1 = Mimi.find_comp(A, :Comp1) + +mi = m.mi +top_i = mi[:top] +A_i = top_i[:A] +comp1_i = A_i[:Comp1] +comp2_i = A_i[:Comp2] +var_2_1 = A_i[:Comp2, :var_2_1] + nothing diff --git a/test/test_explorer.jl b/test/test_explorer.jl index bb9122da9..b7b3f94c1 100644 --- a/test/test_explorer.jl +++ b/test/test_explorer.jl @@ -8,9 +8,7 @@ import Mimi: dataframe_or_scalar, createspec_singlevalue, createspec_lineplot, createspec_multilineplot, createspec_barplot, getmultiline, getline, getbar, _spec_for_item, menu_item_list, explore, - getdataframe, reset_compdefs - -reset_compdefs() + getdataframe @defcomp MyComp begin a = Parameter(index=[time, regions]) diff --git a/test/test_getdataframe.jl b/test/test_getdataframe.jl index 3c527380a..53265f5d3 100644 --- a/test/test_getdataframe.jl +++ b/test/test_getdataframe.jl @@ -3,11 +3,6 @@ module TestGetDataframe using Mimi using Test -import Mimi: - reset_compdefs, _load_dataframe - -reset_compdefs() - #------------------------------------------------------------------------------ # 1. Test with 1 dimension #------------------------------------------------------------------------------ diff --git a/test/test_getindex.jl b/test/test_getindex.jl index 63b18f4ce..18a0a1747 100644 --- a/test/test_getindex.jl +++ b/test/test_getindex.jl @@ -3,11 +3,6 @@ module TestGetIndex using Mimi using Test -import Mimi: - reset_compdefs - -reset_compdefs() - my_model = Model() #Testing that you cannot add two components of the same name diff --git a/test/test_getindex_variabletimestep.jl b/test/test_getindex_variabletimestep.jl index e25aaeb79..3fd3e5ec1 100644 --- a/test/test_getindex_variabletimestep.jl +++ b/test/test_getindex_variabletimestep.jl @@ -3,11 +3,6 @@ module TestGetIndex_VariableTimestep using Mimi using Test -import Mimi: - reset_compdefs - -reset_compdefs() - my_model = Model() #Testing that you cannot add two components of the same name diff --git a/test/test_main.jl b/test/test_main.jl index cc06a080b..74fcef462 100644 --- a/test/test_main.jl +++ b/test/test_main.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - reset_compdefs, reset_variables, @defmodel, + reset_variables, @defmodel, variable, variable_names, external_param, build, compdefs, dimension, compinstance diff --git a/test/test_main_variabletimestep.jl b/test/test_main_variabletimestep.jl index 21af883a3..6e6d4c42b 100644 --- a/test/test_main_variabletimestep.jl +++ b/test/test_main_variabletimestep.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - reset_compdefs, reset_variables, @defmodel, + reset_variables, @defmodel, variable, variable_names, external_param, build, compdef, compdefs, dimension, compinstance diff --git a/test/test_marginal_models.jl b/test/test_marginal_models.jl index 2b8c80135..e730e9d7f 100644 --- a/test/test_marginal_models.jl +++ b/test/test_marginal_models.jl @@ -3,11 +3,6 @@ module TestMarginalModels using Mimi using Test -import Mimi: - reset_compdefs - -reset_compdefs() - @defcomp compA begin varA = Variable(index=[time]) parA = Parameter(index=[time]) diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 2281d626c..eb51dee9b 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -4,10 +4,7 @@ using Test using Mimi import Mimi: - compdef, compname, reset_compdefs, compmodule, first_period, last_period, - variable_names - -reset_compdefs() + compdef, compname, compmodule, first_period, last_period, variable_names @defcomp ch4forcing1 begin c_N2Oconcentration = Parameter(index=[time],unit="ppbv") diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 4d865558e..76a2ee410 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -7,11 +7,9 @@ using Mimi import Mimi: connect_param!, unconnected_params, set_dimension!, has_comp, - reset_compdefs, numcomponents, get_connections, internal_param_conns, dim_count, + numcomponents, get_connections, internal_param_conns, dim_count, dim_names, compdef, getproperty, setproperty!, dimension, compdefs -reset_compdefs() - @defcomp A begin varA::Int = Variable(index=[time]) parA::Int = Parameter() diff --git a/test/test_mult_getdataframe.jl b/test/test_mult_getdataframe.jl index 3f766eba6..059700448 100644 --- a/test/test_mult_getdataframe.jl +++ b/test/test_mult_getdataframe.jl @@ -4,11 +4,6 @@ using Mimi using NamedArrays using Test -import Mimi: - reset_compdefs - -reset_compdefs() - ##################################### # LARGER MULTIREGIONAL TEST (2/3) # ##################################### diff --git a/test/test_parameter_labels.jl b/test/test_parameter_labels.jl index 3df662274..9ffcad632 100644 --- a/test/test_parameter_labels.jl +++ b/test/test_parameter_labels.jl @@ -4,11 +4,6 @@ using Mimi using NamedArrays using Test -import Mimi: - reset_compdefs - -reset_compdefs() - ############################################ # BASIC TEST - use NamedArrays (1/3) # ############################################ diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index 1e9edfd25..b6d860e36 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -206,14 +206,7 @@ set_dimension!(m, :time, 2000:2002) # length 3 add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -<<<<<<< HEAD set_dimension!(m, :time, 1999:2003) # length 5 -======= -@test_logs( - # (:warn, "Redefining dimension :time"), - set_dimension!(m, :time, 1999:2003) # length 5 -) ->>>>>>> master @test_throws ErrorException update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = false) update_param!(m, :x, [2, 3, 4, 5, 6], update_timesteps = true) diff --git a/test/test_plotting.jl b/test/test_plotting.jl index 41a9663e2..16835e51d 100644 --- a/test/test_plotting.jl +++ b/test/test_plotting.jl @@ -3,11 +3,6 @@ module TestPlotting using Mimi using Test -import Mimi: - reset_compdefs - -reset_compdefs() - @defcomp LongComponent begin x = Parameter(index=[time]) y = Parameter() diff --git a/test/test_references.jl b/test/test_references.jl index 7bc3bab46..7a753321b 100644 --- a/test/test_references.jl +++ b/test/test_references.jl @@ -4,9 +4,7 @@ using Test using Mimi import Mimi: - reset_compdefs, getproperty, @defmodel - -reset_compdefs() + getproperty, @defmodel @defcomp Foo begin input = Parameter() diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index 23c862b7c..b05c2a48f 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -3,9 +3,7 @@ module TestReplaceComp using Test using Mimi import Mimi: - reset_compdefs, compdefs, compname, compdef, comp_id, external_param_conns, external_params - -reset_compdefs() + compdefs, compname, compdef, comp_id, external_param_conns, external_params @defcomp X begin x = Parameter(index = [time]) @@ -91,6 +89,7 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs( # (:warn, r".*parameter x no longer exists in component.*"), + (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), replace_comp!(m, bad3, :X) ) diff --git a/test/test_show.jl b/test/test_show.jl index d98163f1b..83e526bc4 100644 --- a/test/test_show.jl +++ b/test/test_show.jl @@ -3,9 +3,7 @@ module TestShow using Test using Mimi import Mimi: - reset_compdefs, compdefs, compdef, ComponentId, MimiStruct, ParameterDef - -# reset_compdefs() + compdefs, compdef, ComponentId, MimiStruct, ParameterDef @defcomp X begin x = Parameter(index = [time]) diff --git a/test/test_timesteparrays.jl b/test/test_timesteparrays.jl index fb9a6ee49..9fbcc214a 100644 --- a/test/test_timesteparrays.jl +++ b/test/test_timesteparrays.jl @@ -5,9 +5,7 @@ using Test import Mimi: FixedTimestep, VariableTimestep, TimestepVector, TimestepMatrix, next_timestep, hasvalue, - isuniform, first_period, last_period, first_and_step, reset_compdefs - -reset_compdefs() + isuniform, first_period, last_period, first_and_step a = collect(reshape(1:16,4,4)) diff --git a/test/test_tmp.jl b/test/test_tmp.jl index af560381d..7bc25a9d6 100644 --- a/test/test_tmp.jl +++ b/test/test_tmp.jl @@ -3,9 +3,7 @@ module Tmp using Test using Mimi import Mimi: - reset_compdefs, compdefs, compdef, external_param_conns - -reset_compdefs() + compdefs, compdef, external_param_conns @defcomp X begin x = Parameter(index = [time]) diff --git a/test/test_tools.jl b/test/test_tools.jl index b4b72c6fe..b60885ea1 100644 --- a/test/test_tools.jl +++ b/test/test_tools.jl @@ -4,9 +4,7 @@ using Test using Mimi import Mimi: - getproperty, reset_compdefs, pretty_string - -reset_compdefs() + getproperty, pretty_string #utils: pretty_string @test pretty_string("camelCaseBasic") == pretty_string(:camelCaseBasic) == "Camel Case Basic" diff --git a/test/test_units.jl b/test/test_units.jl index efaccdd83..1d3164e2d 100644 --- a/test/test_units.jl +++ b/test/test_units.jl @@ -3,9 +3,7 @@ module TestUnits using Test using Mimi -import Mimi: verify_units, connect_param!, ComponentReference, @defmodel, reset_compdefs - -reset_compdefs() +import Mimi: verify_units, connect_param!, ComponentReference, @defmodel # Try directly using verify_units @test verify_units("kg", "kg") diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index 886481d13..c3110fc5b 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -4,13 +4,11 @@ using Mimi using Test import Mimi: - reset_compdefs, variable_names, compinstance, get_var_value, get_param_value, + variable_names, compinstance, get_var_value, get_param_value, set_param_value, set_var_value, dim_count, compdef, ComponentInstance, AbstractComponentInstance, ComponentDef, TimestepArray, ComponentInstanceParameters, ComponentInstanceVariables -reset_compdefs() - my_model = Model() @defcomp testcomp1 begin From 2d2ee20f44f0ee4d1ceb2a6c5f7799cab406d929 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 4 Jun 2019 16:47:46 -0700 Subject: [PATCH 60/81] Further updates to tests --- test/test_components.jl | 16 ++++++++++------ test/test_getdataframe.jl | 2 +- test/test_plotting.jl | 2 ++ test/test_replace_comp.jl | 3 +-- test/test_timesteps.jl | 4 ++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/test/test_components.jl b/test/test_components.jl index b5e81a5c4..709879f6f 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -113,8 +113,10 @@ m = Model() set_dimension!(m, :time, 2001:2005) add_comp!(m, testcomp1, :C) # Don't set the first and last values here cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first === nothing # First and last values should still be nothing because they were not explicitly set -@test cd.last === nothing + +# These tests are not valid in the composite world... +#@test cd.first === nothing # First and last values should still be nothing because they were not explicitly set +#@test cd.last === nothing set_param!(m, :C, :par1, zeros(5)) Mimi.build(m) # Build the model @@ -124,8 +126,10 @@ ci = compinstance(m, :C) # Get the component instance set_dimension!(m, :time, 2005:2020) # Reset the time dimension cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first === nothing # First and last values should still be nothing -@test cd.last === nothing + +# These tests are not valid in the composite world... +#@test cd.first === nothing # First and last values should still be nothing +#@test cd.last === nothing update_param!(m, :par1, zeros(16); update_timesteps=true) Mimi.build(m) # Build the model @@ -151,8 +155,8 @@ cd = compdef(m.md, :C) # Get the component definition in the model # @test cd.last == 2090 # Verify that they didn't change -@test cd.first === nothing -@test cd.last === nothing +#@test cd.first === nothing +#@test cd.last === nothing set_dimension!(m, :time, 2010:2090) diff --git a/test/test_getdataframe.jl b/test/test_getdataframe.jl index 53265f5d3..5be27ff38 100644 --- a/test/test_getdataframe.jl +++ b/test/test_getdataframe.jl @@ -73,7 +73,7 @@ dim = Mimi.dimension(model1, :time) @test all(ismissing, df[:par2][dim[years[end]] : dim[early_last]]) # Test trying to load an item into an existing dataframe where that item key already exists -@test_throws ErrorException _load_dataframe(model1, :testcomp1, :var1, df) +@test_throws UndefVarError _load_dataframe(model1, :testcomp1, :var1, df) #------------------------------------------------------------------------------ diff --git a/test/test_plotting.jl b/test/test_plotting.jl index 16835e51d..dcaf6f7a7 100644 --- a/test/test_plotting.jl +++ b/test/test_plotting.jl @@ -3,6 +3,8 @@ module TestPlotting using Mimi using Test +using Mimi: plot_comp_graph + @defcomp LongComponent begin x = Parameter(index=[time]) y = Parameter() diff --git a/test/test_replace_comp.jl b/test/test_replace_comp.jl index b05c2a48f..bf00cccd2 100644 --- a/test/test_replace_comp.jl +++ b/test/test_replace_comp.jl @@ -89,7 +89,6 @@ set_param!(m, :X, :x, zeros(6)) # Set external parameter for # Replaces with bad3, but warns that there is no parameter by the same name :x @test_logs( # (:warn, r".*parameter x no longer exists in component.*"), - (:warn, "add_comp!: Keyword arguments 'first' and 'last' are currently disabled."), replace_comp!(m, bad3, :X) ) @@ -124,7 +123,7 @@ add_comp!(m, X) @test_throws ErrorException replace_comp!(m, X_repl, :Z) # Component Z does not exist in the model, cannot be replaced -# 8. Test original postion placement functionality +# 8. Test original position placement functionality m = Model() set_dimension!(m, :time, 2000:2005) diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index d023ea781..84582df0e 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -185,7 +185,7 @@ end m2 = Model() set_dimension!(m2, :time, years) bar = add_comp!(m2, Bar) -foo2 = add_comp!(m2, Foo2, first=first_foo) +foo2 = add_comp!(m2, Foo2) # , first=first_foo) set_param!(m2, :Bar, :inputB, collect(1:length(years))) @@ -218,7 +218,7 @@ years = 2000:2010 m3 = Model() set_dimension!(m3, :time, years) -add_comp!(m3, Foo, first=2005) +add_comp!(m3, Foo) #, first=2005) add_comp!(m3, Bar2) set_param!(m3, :Foo, :inputF, 5.) From 31fe7a84de6e8d646ff829295045db605e73a33b Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 4 Jun 2019 16:49:06 -0700 Subject: [PATCH 61/81] WIP -- Debugged composite model. --- src/Mimi.jl | 1 + src/core/build.jl | 4 ++-- src/core/defcomposite.jl | 2 +- src/core/defs.jl | 4 ++-- src/core/instances.jl | 3 --- src/core/paths.jl | 6 +++--- src/core/types/defs.jl | 8 ++++---- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Mimi.jl b/src/Mimi.jl index abeb2f9d0..1df0f1b41 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -70,6 +70,7 @@ include("core/references.jl") include("core/time.jl") include("core/time_arrays.jl") include("core/model.jl") +include("core/order.jl") include("core/paths.jl") include("core/show.jl") diff --git a/src/core/build.jl b/src/core/build.jl index 244033359..92b7da68f 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -225,8 +225,8 @@ function _build(md::ModelDef) not_set = unconnected_params(md) # @info "not_set: $not_set" if ! isempty(not_set) - params = join(not_set, " ") - error("Cannot build model; the following parameters are not set: $params") + params = join(not_set, "\n ") + error("Cannot build model; the following parameters are not set:\n $params") end var_dict = Dict{ComponentPath, Any}() # collect all var defs and diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index b9ec77a7c..95f8927b2 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -117,7 +117,7 @@ functions; these are defined internally to iterate over constituent components a associated method on each. """ macro defcomposite(cc_name, ex) - @info "defining composite $cc_name in module $(fullname(__module__))" + # @info "defining composite $cc_name in module $(fullname(__module__))" @capture(ex, elements__) comps = SubComponent[] diff --git a/src/core/defs.jl b/src/core/defs.jl index 08b0eee39..1805326be 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -521,7 +521,7 @@ not begin with "/", it is treated as relative to `obj`. """ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, param_name::Symbol, value, dims=nothing) # @info "set_param!($(obj.comp_id), $path, $param_name, $value)" - set_param!(obj, _comp_path(obj, path), param_name, value, dims) + set_param!(obj, comp_path(obj, path), param_name, value, dims) end """ @@ -1098,5 +1098,5 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end # Re-add - return add_comp!(obj, comp_id, comp_name; first=first, last=last, before=before, after=after) + return add_comp!(obj, comp_id, comp_name; before=before, after=after) # first=first, last=last, end diff --git a/src/core/instances.jl b/src/core/instances.jl index b7cedfee7..01853a62c 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -27,9 +27,6 @@ end # Setting/getting parameter and variable values # -# -# TBD: once working, explore whether these can be methods of ComponentInstanceData{NT} -# # Get the object stored for the given variable, not the value of the variable. # This is used in the model building process to connect internal parameters. @inline function get_property_obj(obj::ComponentInstanceParameters{NT}, name::Symbol) where {NT} diff --git a/src/core/paths.jl b/src/core/paths.jl index d379c086b..da159c000 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -39,13 +39,13 @@ function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeCompon end """ - _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) + comp_path(node::AbstractCompositeComponentDef, path::AbstractString) Convert a string describing a path from a node to a ComponentPath. The validity of the path is not checked. If `path` starts with "/", the first element in the returned component path is set to the root of the hierarchy containing `node`. """ -function _comp_path(node::AbstractCompositeComponentDef, path::AbstractString) +function comp_path(node::AbstractCompositeComponentDef, path::AbstractString) # empty path means just select the node's path isempty(path) && return node.comp_path @@ -103,7 +103,7 @@ function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) end function find_comp(obj::AbstractCompositeComponentDef, pathstr::AbstractString) - path = _comp_path(obj, pathstr) + path = comp_path(obj, pathstr) find_comp(obj, path) end diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 675c2f69f..8760d3942 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -189,6 +189,10 @@ is_composite(c::AbstractComponentDef) = !is_leaf(c) ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath(obj.comp_path, name) +ComponentPath(obj::AbstractCompositeComponentDef, path::AbstractString) = comp_path(obj, path) + +ComponentPath(obj::AbstractCompositeComponentDef, names::Symbol...) = ComponentPath(obj.comp_path.names..., names...) + @class mutable ModelDef <: CompositeComponentDef begin number_type::DataType dirty::Bool @@ -196,10 +200,6 @@ ComponentPath(obj::AbstractCompositeComponentDef, name::Symbol) = ComponentPath( function ModelDef(number_type::DataType=Float64) self = new() CompositeComponentDef(self) # call super's initializer - - # TBD: now set in CompositeComponentDef(self); delete if that works better - # self.comp_path = ComponentPath(self.name) - return ModelDef(self, number_type, false) # call @class-generated method end end From 0b6341350c992d51b17df8d45b3bab0d84a639dc Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 4 Jun 2019 20:20:50 -0700 Subject: [PATCH 62/81] Added getindex methods for ModelInstance to simplify model interrogation. --- src/core/instances.jl | 46 +++++++++++++++++++++++++++++------ test/test_composite.jl | 55 +++++++++++++++++++++++------------------- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/core/instances.jl b/src/core/instances.jl index 01853a62c..c1ed80ad2 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -139,6 +139,28 @@ parameters(obj::AbstractCompositeComponentInstance, comp_name::Symbol) = paramet parameters(obj::AbstractComponentInstance) = obj.parameters +function Base.getindex(mi::ModelInstance, names::NTuple{N, Symbol}) where N + obj = mi + + # skip past first element if same as root node + if length(names) > 0 && head(obj.comp_path) == names[1] + names = names[2:end] + end + + for name in names + if has_comp(obj, name) + obj = obj[name] + else + error("Component $(obj.comp_path) does not have sub-component :$name") + end + end + return obj +end + +Base.getindex(mi::ModelInstance, comp_path::ComponentPath) = Base.getindex(mi, comp_path.names) + +Base.getindex(mi::ModelInstance, path_str::AbstractString) = Base.getindex(mi, ComponentPath(mi.md, path_str)) + function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol) if ! has_comp(obj, comp_name) error("Component :$comp_name does not exist in the given composite") @@ -146,17 +168,18 @@ function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbo return compinstance(obj, comp_name) end -function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol, datum_name::Symbol) - comp_inst = obj[comp_name] - vars = variables(comp_inst) - pars = parameters(comp_inst) - +function _get_datum(ci::AbstractComponentInstance, datum_name::Symbol) + vars = variables(ci) + if datum_name in names(vars) which = vars - elseif datum_name in names(pars) - which = pars else - error("$datum_name is not a parameter or a variable in component $comp_name.") + pars = parameters(ci) + if datum_name in names(pars) + which = pars + else + error("$datum_name is not a parameter or a variable in component $(ci.comp_path).") + end end value = getproperty(which, datum_name) @@ -164,6 +187,13 @@ function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbo return value isa TimestepArray ? value.data : value end +Base.getindex(mi::ModelInstance, key, datum::Symbol) = _get_datum(mi[key], datum) + +function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol, datum::Symbol) + ci = obj[comp_name] + return _get_datum(ci, datum) +end + """ dim_count(mi::ModelInstance, dim_name::Symbol) diff --git a/test/test_composite.jl b/test/test_composite.jl index 33ac7c296..f538a56b4 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -42,7 +42,7 @@ end @defcomp Comp4 begin par_4_1 = Parameter(index=[time]) # connected to Comp2.var_2_1 - var_41 = Variable(index=[time]) # external output + var_4_1 = Variable(index=[time]) # external output foo = Parameter(default=300) function run_timestep(p, v, d, t) @@ -59,14 +59,16 @@ set_dimension!(m, :time, 2005:2020) component(Comp2, exports=[foo => foo2]) end -# @defcomposite B begin -# component(Comp3, exports=[foo => foo3]) #bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) -# component(Comp4, exports=[foo => foo4]) -# end +@defcomposite B begin + component(Comp3, exports=[foo => foo3]) # bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) + component(Comp4, exports=[foo => foo4]) +end @defcomposite top begin component(A; exports=[foo1 => fooA1, foo2 => fooA2]) - # component(B; exports=[foo3, foo4]) + + # TBD: component B isn't getting added to mi + component(B; exports=[foo3, foo4]) end # We have created the following composite structure: @@ -87,22 +89,27 @@ md = m.md c1 = find_comp(md, ComponentPath(:top, :A, :Comp1)) @test c1.comp_id == Comp1.comp_id -# c3 = find_comp(md, "/top/B/Comp3") -# @test c3.comp_id == Comp3.comp_id +c3 = find_comp(md, "/top/B/Comp3") +@test c3.comp_id == Comp3.comp_id set_param!(m, "/top/A/Comp1", :foo, 1) set_param!(m, "/top/A/Comp2", :foo, 2) +# TBD: default values set in @defcomp are not working... +set_param!(m, "/top/B/Comp3", :foo, 10) +set_param!(m, "/top/B/Comp4", :foo, 20) + set_param!(m, "/top/A/Comp1", :par_1_1, collect(1:length(time_labels(md)))) c1_path = ComponentPath(md, :top, :A, :Comp1) c2_path = ComponentPath(md, "/top/A/Comp2") -# c3_path = ComponentPath(md, "/top/B/Comp3") -# c4_path = ComponentPath(md, "top/B/Comp4") +c3_path = ComponentPath(md, "/top/B/Comp3") +c4_path = ComponentPath(md, "/top/B/Comp4") connect_param!(md, c2_path, :par_2_1, c1_path, :var_1_1) -connect_param!(top_comp, c2_path, :par_2_2, c1_path, :var_1_1) -# connect_param!(top_comp, c3_path, :par_3_1, c2_path, :var_2_1) +connect_param!(md, c2_path, :par_2_2, c1_path, :var_1_1) +connect_param!(md, c3_path, :par_3_1, c2_path, :var_2_1) +connect_param!(md, c4_path, :par_4_1, c3_path, :var_3_1) build(m) run(m) @@ -110,7 +117,7 @@ run(m) # # TBD # -# 1. Create parallel structure of exported vars/pars in Instance hierarchy +# 1. Create parallel structure of exported vars/pars in Instance hierarchy? # - Perhaps just a dict mapping local name to a component path under mi, to where the var actually exists # 2. Be able to connect to the leaf version of vars/pars or by specifying exported version below the compdef # given as first arg to connect_param!(). @@ -118,21 +125,19 @@ run(m) # 4. get_index should work for any comp/var/par below the given compinstance # - also support indexing by a path or tuple indicating a path below the given instance +mi = m.mi +@test mi["/top/A/Comp1", :var_1_1] == collect(1.0:16.0) +@test mi[c2_path, :par_2_2] == collect(1.0:16.0) +@test mi[c2_path.names, :var_2_1] == collect(3.0:3:48.0) +@test mi["/top/B/Comp4", :par_4_1] == collect(6.0:6:96.0) end # module -m = TestComposite.m -md = m.md -top = Mimi.find_comp(md, :top) -A = Mimi.find_comp(top, :A) -comp1 = Mimi.find_comp(A, :Comp1) - -mi = m.mi -top_i = mi[:top] -A_i = top_i[:A] -comp1_i = A_i[:Comp1] -comp2_i = A_i[:Comp2] -var_2_1 = A_i[:Comp2, :var_2_1] +# m = TestComposite.m +# md = m.md +# top = Mimi.find_comp(md, :top) +# A = Mimi.find_comp(top, :A) +# comp1 = Mimi.find_comp(A, :Comp1) nothing From 29fe5c9e5125ef71bf67cfc75ec852cb5e759d3a Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 5 Jun 2019 14:56:34 -0700 Subject: [PATCH 63/81] Added methods to use strings representing data elements in a component, e.g., "/my/comp/path:datum" in set_param! and connect_param!. --- src/Mimi.jl | 2 +- src/core/connections.jl | 44 +++++++++++++++----- src/core/defs.jl | 14 ++++++- src/core/model.jl | 11 +++++ src/core/types/defs.jl | 5 +-- src/core/types/{_includes.jl => includes.jl} | 0 test/test_composite.jl | 31 +++++++------- 7 files changed, 74 insertions(+), 33 deletions(-) rename src/core/types/{_includes.jl => includes.jl} (100%) diff --git a/src/Mimi.jl b/src/Mimi.jl index 1df0f1b41..1663bc791 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -54,7 +54,7 @@ export variable_names include("core/delegate.jl") -include("core/types/_includes.jl") +include("core/types/includes.jl") # # After loading types and delegation macro, the rest can be loaded in any order. # diff --git a/src/core/connections.jl b/src/core/connections.jl index be4bf30f0..f6a6495d9 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -248,6 +248,33 @@ function connect_param!(obj::AbstractCompositeComponentDef, connect_param!(obj, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset) end +""" + split_datum_path(obj::AbstractCompositeComponentDef, s::AbstractString) + +Split a string of the form "/path/to/component:datum_name" into the component path, +`ComponentPath(:path, :to, :component)` and name `:datum_name`. +""" +function split_datum_path(obj::AbstractCompositeComponentDef, s::AbstractString) + elts = split(s, ":") + length(elts) != 2 && error("Can't split datum path '$s' into ComponentPath and datum name") + return (ComponentPath(obj, elts[1]), Symbol(elts[2])) +end + +""" +Connect a parameter and variable using string notation "/path/to/component:datum_name" where +the potion before the ":" is the string representation of a component path from `obj` and the +portion after is the name of the src or dst datum. +""" +function connect_param!(obj::AbstractCompositeComponentDef, dst::AbstractString, src::AbstractString, + backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0) + dst_path, dst_name = split_datum_path(obj, dst) + src_path, src_name = split_datum_path(obj, src) + + connect_param!(obj, dst_path, dst_name, src_path, src_name, + backup; ignoreunits=ignoreunits, offset=offset) +end + + const ParamVector = Vector{ParamPath} _collect_connected_params(obj::ComponentDef, connected) = nothing @@ -344,17 +371,11 @@ function external_param_conns(obj::AbstractCompositeComponentDef, comp_name::Sym end function external_param(obj::AbstractCompositeComponentDef, name::Symbol; missing_ok=false) - try - return obj.external_params[name] - catch err - if err isa KeyError - missing_ok && return nothing + haskey(obj.external_params, name) && return obj.external_params[name] - error("$name not found in external parameter list") - else - rethrow(err) - end - end + missing_ok && return nothing + + error("$name not found in external parameter list") end function add_internal_param_conn!(obj::AbstractCompositeComponentDef, conn::InternalParameterConnection) @@ -368,6 +389,9 @@ function add_external_param_conn!(obj::AbstractCompositeComponentDef, conn::Exte end function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::ModelParameter) + if haskey(obj.external_params, name) + @warn "Redefining external param :$name in $(obj.comp_path) from $(obj.external_params[name]) to $value" + end obj.external_params[name] = value dirty!(obj) end diff --git a/src/core/defs.jl b/src/core/defs.jl index 1805326be..03e63587b 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,4 +1,3 @@ - function find_module(path::NTuple{N, Symbol} where N) m = Main for name in path @@ -484,7 +483,7 @@ end value_dict::Dict{Symbol, Any}, param_names) Call `set_param!()` for each name in `param_names`, retrieving the corresponding value from -`value_dict[param_name`. +`value_dict[param_name]`. """ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, value_dict::Dict{Symbol, Any}, param_names) for param_name in param_names @@ -524,6 +523,17 @@ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, pa set_param!(obj, comp_path(obj, path), param_name, value, dims) end +""" + set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, value, dims=nothing) + +Set a parameter using a colon-delimited string to specify the component path (before the ":") +and the param name (after the ":"). +""" +function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, value, dims=nothing) + comp_path, param_name = split_datum_path(obj, path) + set_param!(obj, comp_path, param_name, value, dims) +end + """ set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, name::Symbol, value, dims=nothing) diff --git a/src/core/model.jl b/src/core/model.jl index 191874851..707790acb 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -57,6 +57,10 @@ component parameter should only be calculated for the second timestep and beyond @delegate connect_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) => md +@delegate connect_param!(m::Model, dst::AbstractString, src::AbstractString, backup::Union{Nothing, Array}=nothing; + ignoreunits::Bool=false, offset::Int=0) => md + + """ connect_param!(m::Model, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol}, backup::Array; ignoreunits::Bool=false) @@ -327,6 +331,13 @@ relative to `m.md`, which at the top of the hierarchy, produces the same result """ @delegate set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) => md +""" + set_param!(m::Model, path::AbstractString, value, dims=nothing) + +Similar to above but param_name appears in `path` after a colon delimiter. +""" +@delegate set_param!(m::Model, path::AbstractString, value, dims=nothing) => md + """ run(m::Model) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 8760d3942..8efd600ba 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -121,7 +121,7 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} - external_params::Dict{Symbol, ModelParameter} + external_params::Dict{Symbol, ModelParameter} # TBD: make key (ComponentPath, Symbol)? # Names of external params that the ConnectorComps will use as their :input2 parameters. backups::Vector{Symbol} @@ -171,11 +171,10 @@ function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Ve return composite end -# TBD: these should dynamically and recursively compute the lists +# TBD: Recursively compute the lists on demand? internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns -# TBD: should only ModelDefs have external params? external_params(obj::AbstractCompositeComponentDef) = obj.external_params exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) diff --git a/src/core/types/_includes.jl b/src/core/types/includes.jl similarity index 100% rename from src/core/types/_includes.jl rename to src/core/types/includes.jl diff --git a/test/test_composite.jl b/test/test_composite.jl index f538a56b4..d477a651c 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -92,24 +92,21 @@ c1 = find_comp(md, ComponentPath(:top, :A, :Comp1)) c3 = find_comp(md, "/top/B/Comp3") @test c3.comp_id == Comp3.comp_id -set_param!(m, "/top/A/Comp1", :foo, 1) -set_param!(m, "/top/A/Comp2", :foo, 2) +set_param!(m, "/top/A/Comp1:foo", 1) +set_param!(m, "/top/A/Comp2:foo", 2) # TBD: default values set in @defcomp are not working... -set_param!(m, "/top/B/Comp3", :foo, 10) -set_param!(m, "/top/B/Comp4", :foo, 20) +# Also, external_parameters are stored in the parent, so both of the +# following set parameter :foo in "/top/B", with 2nd overwriting 1st. +set_param!(m, "/top/B/Comp3:foo", 10) +set_param!(m, "/top/B/Comp4:foo", 20) set_param!(m, "/top/A/Comp1", :par_1_1, collect(1:length(time_labels(md)))) -c1_path = ComponentPath(md, :top, :A, :Comp1) -c2_path = ComponentPath(md, "/top/A/Comp2") -c3_path = ComponentPath(md, "/top/B/Comp3") -c4_path = ComponentPath(md, "/top/B/Comp4") - -connect_param!(md, c2_path, :par_2_1, c1_path, :var_1_1) -connect_param!(md, c2_path, :par_2_2, c1_path, :var_1_1) -connect_param!(md, c3_path, :par_3_1, c2_path, :var_2_1) -connect_param!(md, c4_path, :par_4_1, c3_path, :var_3_1) +connect_param!(m, "/top/A/Comp2:par_2_1", "/top/A/Comp1:var_1_1") +connect_param!(m, "/top/A/Comp2:par_2_2", "/top/A/Comp1:var_1_1") +connect_param!(m, "/top/B/Comp3:par_3_1", "/top/A/Comp2:var_2_1") +connect_param!(m, "/top/B/Comp4:par_4_1", "/top/B/Comp3:var_3_1") build(m) run(m) @@ -122,14 +119,14 @@ run(m) # 2. Be able to connect to the leaf version of vars/pars or by specifying exported version below the compdef # given as first arg to connect_param!(). # 3. set_param!() should work with relative path from any compdef. -# 4. get_index should work for any comp/var/par below the given compinstance -# - also support indexing by a path or tuple indicating a path below the given instance +# 4. set_param!() stores external_parameters in the parent object, creating namespace conflicts between comps. +# Either store these in the leaf or store them with a key (comp_name, param_name) mi = m.mi @test mi["/top/A/Comp1", :var_1_1] == collect(1.0:16.0) -@test mi[c2_path, :par_2_2] == collect(1.0:16.0) -@test mi[c2_path.names, :var_2_1] == collect(3.0:3:48.0) +@test mi[ComponentPath(md, :top, :A, :Comp2), :par_2_2] == collect(1.0:16.0) +@test mi[(:top, :A, :Comp2), :var_2_1] == collect(3.0:3:48.0) @test mi["/top/B/Comp4", :par_4_1] == collect(6.0:6:96.0) end # module From 0a810006afc218eda232dc4c1ca9769f449e92ca Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 7 Jun 2019 18:38:15 -0700 Subject: [PATCH 64/81] WIP --- src/core/paths.jl | 2 +- src/core/types/params.jl | 2 +- src/mcs/defmcs.jl | 2 +- src/mcs/mcs_types.jl | 9 +++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/paths.jl b/src/core/paths.jl index da159c000..9ce379a01 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -151,4 +151,4 @@ gensym("ModelDef") names look like Symbol("##ModelDef#123") """ function is_abspath(path::ComponentPath) return ! isempty(path) && match(r"^##ModelDef#\d+$", string(path.names[1])) !== nothing -end \ No newline at end of file +end diff --git a/src/core/types/params.jl b/src/core/types/params.jl index 9f718d863..fd39ffef3 100644 --- a/src/core/types/params.jl +++ b/src/core/types/params.jl @@ -4,7 +4,7 @@ abstract type ModelParameter <: MimiStruct end -# TBD: rename ScalarParameter, ArrayParameter, and AbstractParameter? +# TBD: rename as ScalarParameter, ArrayParameter, and AbstractParameter? mutable struct ScalarModelParameter{T} <: ModelParameter value::T diff --git a/src/mcs/defmcs.jl b/src/mcs/defmcs.jl index 08b49a130..6baf53af1 100644 --- a/src/mcs/defmcs.jl +++ b/src/mcs/defmcs.jl @@ -164,7 +164,7 @@ macro defsim(expr) return :(Simulation{$simdatatype}( [$(_rvs...)], - [$(_transforms...)], + TransformSpec[$(_transforms...)], Tuple{Symbol, Symbol}[$(_saves...)], $data)) end diff --git a/src/mcs/mcs_types.jl b/src/mcs/mcs_types.jl index 906536970..b83c0938f 100644 --- a/src/mcs/mcs_types.jl +++ b/src/mcs/mcs_types.jl @@ -161,6 +161,15 @@ mutable struct Simulation{T} end end +""" + Simulation{T}() where T <: AbstractSimulationData + +Allow creation of an "empty" Simulation instance. +""" +function Simulation{T}() where T <: AbstractSimulationData + Simulation{T}([], TransformSpec[], Tuple{Symbol, Symbol}[], T()) +end + """ set_payload!(sim::Simulation, payload) From aa517cd42aa9837ceb396f48d28b108e82dbb984 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 13 Jun 2019 11:45:37 -0700 Subject: [PATCH 65/81] - Add a combined sub-component/param/var namespace Dict to CompositeComponentDef - Add getindex/setindex/haskey for CompositeComponentDef's to operate on namespace rather than always assuming this identifies a sub-comp. --- src/core/defs.jl | 44 ++++++++++++++++++++++++++++++++---------- src/core/types/defs.jl | 4 ++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 03e63587b..771941d04 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -28,7 +28,6 @@ compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.comps_dict[comp_name] - has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) @@ -45,9 +44,6 @@ compname(obj::AbstractComponentDef) = compname(obj.comp_id) compnames() = map(compname, compdefs()) -# Access a subcomponent as comp[:name] -Base.getindex(obj::AbstractCompositeComponentDef, name::Symbol) = obj.comps_dict[name] - """ is_detached(obj::AbstractComponentDef) @@ -131,6 +127,30 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) filter!(epc_filter, ccd.external_param_conns) end +# +# Add [] indexing and key lookup to composites to access namespace +# +@delegate Base.haskey(comp::AbstractCompositeComponentDef, key::Symbol) => namespace +@delegate Base.getindex(comp::AbstractCompositeComponentDef, key::Symbol) => namespace + +function Base.setindex!(comp::AbstractCompositeComponentDef, value::T, key::Symbol) where T <: NamespaceElement + # Allow replacement of existing values for a key only with items of the same type. + if haskey(comp, key) + elt_type = typeof(comp[key]) + elt_type == T || error("Cannot replace item $key, type $elt_type, with object type $T in component $(comp.comp_path).") + end + + comp.namespace[key] = value + + # Also store in comps_dict, if value is a component + # TBD: drop comps_dict in favor of namespace for both purposes + if value isa AbstractComponentDef + comp.comps_dict[key] = value + end + + return value +end + # # Dimensions # @@ -748,10 +768,6 @@ end # Model # -function _append_comp!(obj::AbstractCompositeComponentDef, comp_name::Symbol, comp_def::AbstractComponentDef) - obj.comps_dict[comp_name] = comp_def -end - function _add_anonymous_dims!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef) for (name, dim) in filter(pair -> pair[2] !== nothing, comp_def.dim_dict) # @info "Setting dimension $name to $dim" @@ -760,7 +776,15 @@ function _add_anonymous_dims!(obj::AbstractCompositeComponentDef, comp_def::Abst end function comps_dict!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) - obj.comps_dict = comps + for key in keys(obj.comps_dict) + delete!(obj.namespace, key) + end + empty!(obj.comps_dict) + + for (key, value) in comps + obj[key] = value # adds to both comps_dict and namespace + end + dirty!(obj) end @@ -783,7 +807,7 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom comp_name = nameof(comp_def) if before === nothing && after === nothing - _append_comp!(obj, comp_name, comp_def) # add it to the end + obj[comp_name] = comp_def # add to comps_dict and to namespace else new_comps = OrderedDict{Symbol, AbstractComponentDef}() diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 8efd600ba..a46538532 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -114,6 +114,8 @@ end global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} global const ExportsDict = Dict{Symbol, AbstractDatumReference} +global const NamespaceElement = Union{ParameterDefReference, VariableDefReference, AbstractComponentDef} + @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Binding} @@ -123,6 +125,8 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} external_param_conns::Vector{ExternalParameterConnection} external_params::Dict{Symbol, ModelParameter} # TBD: make key (ComponentPath, Symbol)? + namespace::Dict{Symbol, NamespaceElement} + # Names of external params that the ConnectorComps will use as their :input2 parameters. backups::Vector{Symbol} From ccb2c6706cbfa1ff560b095f3ca38342891d8ea8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 18 Jun 2019 14:35:33 -0700 Subject: [PATCH 66/81] WIP - Support for importing subcomp params and vars in a composite, and for making connections in @defcomposite --- src/core/connections.jl | 2 +- src/core/defcomp.jl | 28 +++++++++- src/core/defcomposite.jl | 109 +++++++++++++++++++++++++++++++++++--- src/core/defs.jl | 93 ++++++++++++++------------------ src/core/model.jl | 8 +++ src/core/paths.jl | 14 ++++- src/core/types/defs.jl | 33 +++++++----- test/test_defcomposite.jl | 68 ++++++++++++++++++++++++ 8 files changed, 280 insertions(+), 75 deletions(-) create mode 100644 test/test_defcomposite.jl diff --git a/src/core/connections.jl b/src/core/connections.jl index f6a6495d9..92a3c2920 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -324,7 +324,7 @@ function unconnected_params(md::ModelDef) connected = connected_params(md) _collect_unconnected_params(md, connected, unconnected) - return unconnected + return unconnected end """ diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 8c32f6c55..db3ab7ce1 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -68,9 +68,35 @@ function _check_for_known_element(name) end end +# Add a variable to a ComponentDef. CompositeComponents have no vars of their own, +# only references to vars in components contained within. +function add_variable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) + v = VariableDef(name, comp_def.comp_path, datatype, dimensions, description, unit) + comp_def[name] = v # adds to namespace and checks for duplicate + comp_def.variables[name] = v + return v +end + +# Add a variable to a ComponentDef referenced by ComponentId +function add_variable(comp_id::ComponentId, name, datatype, dimensions, description, unit) + add_variable(compdef(comp_id), name, datatype, dimensions, description, unit) +end + +function add_parameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) + p = ParameterDef(name, comp_def.comp_path, datatype, dimensions, description, unit, default) + comp_def[name] = p # adds to namespace and checks for duplicate + comp_def.parameters[name] = p + dirty!(comp_def) + return p +end + +function add_parameter(comp_id::ComponentId, name, datatype, dimensions, description, unit, default) + add_parameter(compdef(comp_id), name, datatype, dimensions, description, unit, default) +end + # Generates an expression to construct a Variable or Parameter function _generate_var_or_param(elt_type, name, datatype, dimensions, dflt, desc, unit) - func_name = elt_type == :Parameter ? :addparameter : :addvariable + func_name = elt_type == :Parameter ? :add_parameter : :add_variable args = [datatype, dimensions, desc, unit] if elt_type == :Parameter push!(args, dflt) diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 95f8927b2..c4b0111ce 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -89,6 +89,29 @@ function _subcomp(args, kwargs) return SubComponent(cmodule, cname, alias, exports, bindings) end +# Convert an expr like `a.b.c.d` to `[:a, :b, :c, :d]` +function _parse_dotted_symbols(expr) + global Args = expr + syms = Symbol[] + + ex = expr + while @capture(ex, left_.right_) && right isa Symbol + push!(syms, right) + ex = left + end + + if ex isa Symbol + push!(syms, ex) + else + error("Expected Symbol or Symbol.Symbol..., got $expr") + end + + syms = reverse(syms) + var_or_par = pop!(syms) + return ComponentPath(syms), var_or_par +end + + """ defcomposite(cc_name::Symbol, ex::Expr) @@ -121,27 +144,99 @@ macro defcomposite(cc_name, ex) @capture(ex, elements__) comps = SubComponent[] + imports = [] + conns = [] for elt in elements + # @info "parsing $elt"; dump(elt) + if @capture(elt, (component(args__; kwargs__) | component(args__))) push!(comps, _subcomp(args, kwargs)) + + # distinguish imports, e.g., :(EXP_VAR = CHILD_COMP1.COMP2.VAR3), + # from connections, e.g., :(COMP1.PAR2 = COMP2.COMP5.VAR2) + + # elseif elt.head == :tuple && length(elt.args) > 0 && @capture(elt.args[1], left_ = right_) && left isa Symbol + # # Aliasing a local name to several parameters at once is possible using an expr like + # # :(EXP_PAR1 = CHILD_COMP1.PAR2, CHILD_COMP2.PAR2, CHILD_COMP3.PAR5, CHILD_COMP3.PAR6) + # # Note that this parses as a tuple expression with first element being `EXP_PAR1 = CHILD_COMP1`. + # # Here we parse everything on the right side, at once using broadcasting and add the initial + # # component (immediately after "=") to the list, and then store a Vector of param refs. + # args = [right, elt.args[2:end]...] + # vars_pars = _parse_dotted_symbols.(args) + # @info "import as $left = $vars_pars" + # push!(imports, (left, vars_pars)) + + elseif @capture(elt, left_ = right_) + + if left isa Symbol # simple import case + # Save a singletons as a 1-element Vector for consistency with multiple linked params + var_par = right.head == :tuple ? _parse_dotted_symbols.(right.args) : [_parse_dotted_symbols(right)] + push!(imports, (left, var_par)) + @info "import as $left = $var_par" + + # note that `comp_Symbol.name_Symbol` failed; bug in MacroTools? + elseif @capture(left, comp_.name_) && comp isa Symbol && name isa Symbol # simple connection case + src = _parse_dotted_symbols(right) + dst = _parse_dotted_symbols(left) + tup = (dst, src) + push!(conns, tup) + @info "connection: $dst = $src" + + else + error("Unrecognized expression on left hand side of '=' in @defcomposite: $elt") + end else error("Unrecognized element in @defcomposite: $elt") end end - # module_name = nameof(__module__) - # TBD: use fullname(__module__) to get "path" to module, as tuple of Symbols, e.g., (:Main, :ABC, :DEF) # TBD: use Base.moduleroot(__module__) to get the first in that sequence, if needed # TBD: parentmodule(m) gets the enclosing module (but for root modules returns self) # TBD: might need to replace the single symbol used for module name in ComponentId with Module path. - result = quote - cc_id = Mimi.ComponentId($__module__, $(QuoteNode(cc_name))) - global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, $__module__) - $cc_name - end + # @info "imports: $imports" + # @info " $(length(imports)) elements" + # global IMP = imports + + result = :( + let conns = $conns, + imports = $imports + cc_id = Mimi.ComponentId($__module__, $(QuoteNode(cc_name))) + + global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, $__module__) + + for ((dst_path, dst_name), (src_path, src_name)) in conns + Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) + end + + for (local_name, var_par_vec) in imports + refs = [] + + for (src_path, src_name) in var_par_vec + dr = Mimi.DatumReference(src_name, $cc_name, src_path) + var_par_ref = (Mimi.is_parameter(dr) ? Mimi.ParameterDefReference(dr) : Mimi.VariableDefReference(dr)) + push!(refs, var_par_ref) + end + + # we allow linking parameters, but not variables. + count = length(refs) + if count == 1 && refs[1] isa Mimi.VariableDefReference + $cc_name[local_name] = refs[1] # store single VariableDefReference; multiples not supported + elseif count > 1 + vars = filter(obj -> obj isa Mimi.VariableDefReference, refs) + if length(vars) > 0 + error("Variables ($vars) must be aliased only individually.") + else + $cc_name[local_name] = Vector{Mimi.ParameterDefReference}(refs) # fix type + end + end + end + + $cc_name + end + ) # @info "defcomposite:\n$result" return esc(result) diff --git a/src/core/defs.jl b/src/core/defs.jl index 771941d04..9f2317c0d 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -68,13 +68,9 @@ end dirty!(md::ModelDef) = (md.dirty = true) compname(dr::AbstractDatumReference) = dr.comp_path.names[end] -#@delegate compmodule(dr::DatumReference) => comp_id -is_variable(dr::AbstractDatumReference) = false -is_parameter(dr::AbstractDatumReference) = false - -is_variable(dr::VariableDefReference) = has_variable(find_comp(dr), nameof(dr)) -is_parameter(dr::ParameterDefReference) = has_parameter(find_comp(dr), nameof(dr)) +is_variable(dr::AbstractDatumReference) = has_variable(find_comp(dr), nameof(dr)) +is_parameter(dr::AbstractDatumReference) = has_parameter(find_comp(dr), nameof(dr)) number_type(md::ModelDef) = md.number_type @@ -127,13 +123,32 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) filter!(epc_filter, ccd.external_param_conns) end -# -# Add [] indexing and key lookup to composites to access namespace -# -@delegate Base.haskey(comp::AbstractCompositeComponentDef, key::Symbol) => namespace -@delegate Base.getindex(comp::AbstractCompositeComponentDef, key::Symbol) => namespace +@delegate Base.haskey(comp::AbstractComponentDef, key::Symbol) => namespace + +function Base.getindex(comp::AbstractComponentDef, key::Symbol) + value = comp.namespace[key] + + value isa AbstractComponentDef && return value + + # Variables can't be linked (not an array of values). + # If there are linked params, all have the same value, use first. + # If not linked, params are still stored as vector of length 1. + ref = (value isa Vector ? value[1] : value) + + # follow reference to access value of parameter + obj = find_comp(ref.root, ref.comp_path) + obj === nothing && error("Failed to find referenced parameter: $ref") + + return obj[ref.name] +end + + +function Base.setindex!(comp::AbstractComponentDef, value::T, key::Symbol) where T <: NamespaceElement + # For leaf components, setindex! can store only vars and vectors of pars + if comp isa ComponentDef && T isa AbstractComponentDef + error("Cannot store components in leaf component $(comp.comp_path)") + end -function Base.setindex!(comp::AbstractCompositeComponentDef, value::T, key::Symbol) where T <: NamespaceElement # Allow replacement of existing values for a key only with items of the same type. if haskey(comp, key) elt_type = typeof(comp[key]) @@ -412,17 +427,6 @@ end # Callable on both ParameterDef and VariableDef dim_names(obj::AbstractDatumDef) = obj.dim_names -function addparameter(comp_def::AbstractComponentDef, name, datatype, dimensions, description, unit, default) - p = ParameterDef(name, comp_def.comp_path, datatype, dimensions, description, unit, default) - comp_def.parameters[name] = p - dirty!(comp_def) - return p -end - -function addparameter(comp_id::ComponentId, name, datatype, dimensions, description, unit, default) - addparameter(compdef(comp_id), name, datatype, dimensions, description, unit, default) -end - """ parameters(comp_def::ComponentDef) @@ -554,6 +558,20 @@ function set_param!(obj::AbstractCompositeComponentDef, path::AbstractString, va set_param!(obj, comp_path, param_name, value, dims) end +""" + set_param!(obj::AbstractCompositeComponentDef, param_name::Symbol, value, dims=nothing) + +Set the value of a parameter exposed in `obj` by following the ParameterDefReference. This +method cannot be used on composites that are subcomponents of another composite. +""" +function set_param!(obj::AbstractCompositeComponentDef, param_name::Symbol, value, dims=nothing) + if obj.parent !== nothing + error("Parameter setting is supported only for top-level composites. $(obj.comp_path) is a subcomponent.") + end + param_ref = obj[param_name] + set_param!(obj, param_ref.comp_path, param_ref.name, value, dims=dims) +end + """ set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, name::Symbol, value, dims=nothing) @@ -716,35 +734,6 @@ function variable_dimensions(obj::AbstractComponentDef, name::Symbol) return dim_names(var) end - -# Add a variable to a ComponentDef. CompositeComponents have no vars of their own, -# only references to vars in components contained within. -function addvariable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) - var_def = VariableDef(name, comp_def.comp_path, datatype, dimensions, description, unit) - comp_def.variables[name] = var_def - return var_def -end - -# -# TBD: this is clearly not used because there is no "variable" (other than the func) defined here -# -""" - addvariables(obj::CompositeComponentDef, exports::Vector{Pair{AbstractDatumReference, Symbol}}) - -Add all exported variables to a CompositeComponentDef. -""" -function addvariables(obj::AbstractCompositeComponentDef, exports::Vector{Pair{AbstractDatumReference, Symbol}}) - # TBD: this needs attention - for (dr, exp_name) in exports - addvariable(obj, variable(obj, nameof(variable)), exp_name) - end -end - -# Add a variable to a ComponentDef referenced by ComponentId -function addvariable(comp_id::ComponentId, name, datatype, dimensions, description, unit) - addvariable(compdef(comp_id), name, datatype, dimensions, description, unit) -end - # # Other # diff --git a/src/core/model.jl b/src/core/model.jl index 707790acb..aae0faa9a 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -338,6 +338,14 @@ Similar to above but param_name appears in `path` after a colon delimiter. """ @delegate set_param!(m::Model, path::AbstractString, value, dims=nothing) => md +""" + set_param!(m::Model, param_name::Symbol, value, dims=nothing) + +Set the value of a parameter exposed in the ModelDef (m.md). +""" +@delegate set_param!(m::Model, param_name::Symbol, value, dims=nothing) => md + + """ run(m::Model) diff --git a/src/core/paths.jl b/src/core/paths.jl index 9ce379a01..8e27344e9 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -21,13 +21,25 @@ end comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) Set the ComponentPath of a child object to extend the path of its composite parent. -For leaf components, also sets the ComponentPath in all ParameterDefs and VariableDefs. +For composites, also update the component paths for all internal connections. +For leaf components, update the ComponentPath for ParameterDefs and VariableDefs. """ function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) child.comp_path = path = ComponentPath(parent.comp_path, child.name) # recursively reset all comp_paths if is_composite(child) + + conns = child.internal_param_conns + for (i, conn) in enumerate(conns) + src_path = ComponentPath(path, conn.src_comp_path) + dst_path = ComponentPath(path, conn.dst_comp_path) + + # InternalParameterConnections are immutable, but the vector holding them is not + conns[i] = InternalParameterConnection(src_path, conn.src_var_name, dst_path, conn.dst_par_name, + conn.ignoreunits, conn.backup; offset=conn.offset) + end + for cd in compdefs(child) comp_path!(cd, child) end diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index a46538532..277aaa461 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -40,13 +40,16 @@ end variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} + namespace::Dict{Symbol, Any} first::Union{Nothing, Int} last::Union{Nothing, Int} is_uniform::Bool # Store a reference to the AbstractCompositeComponent that contains this comp def. - # That type is defined later, so we declare Any here. - parent::Union{Nothing, Any} + # That type is defined later, so we declare Any here. Parent is `nothing` for + # detached (i.e., "template") components and is set when added to a composite. + parent::Any + function ComponentDef(self::ComponentDef, comp_id::Nothing) error("Leaf ComponentDef objects must have a valid ComponentId name (not nothing)") @@ -68,6 +71,7 @@ end self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() + self.namespace = Dict{Symbol, Any}() self.first = self.last = nothing self.is_uniform = true self.parent = nothing @@ -90,6 +94,15 @@ isuniform(obj::AbstractComponentDef) = obj.is_uniform Base.parent(obj::AbstractComponentDef) = obj.parent +# Used by @defcomposite to communicate subcomponent information +struct SubComponent <: MimiStruct + module_name::Union{Nothing, Symbol} + comp_name::Symbol + alias::Union{Nothing, Symbol} + exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} + bindings::Vector{Pair{Symbol, Any}} +end + # Stores references to the name of a component variable or parameter # and the ComponentPath of the component in which it is defined @class DatumReference <: NamedObj begin @@ -99,22 +112,18 @@ Base.parent(obj::AbstractComponentDef) = obj.parent end @class ParameterDefReference <: DatumReference + @class VariableDefReference <: DatumReference -# Used by @defcomposite to communicate subcomponent information -struct SubComponent <: MimiStruct - module_name::Union{Nothing, Symbol} - comp_name::Symbol - alias::Union{Nothing, Symbol} - exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} - bindings::Vector{Pair{Symbol, Any}} -end +# "Upgrade" a DatumReference to one of the specific types +ParameterDefReference(dr::DatumReference) = ParameterDefReference(dr.name, dr.root, dr.comp_path) +VariableDefReference(dr::DatumReference) = VariableDefReference(dr.name, dr.root, dr.comp_path) # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} global const ExportsDict = Dict{Symbol, AbstractDatumReference} -global const NamespaceElement = Union{ParameterDefReference, VariableDefReference, AbstractComponentDef} +global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference, Vector{ParameterDefReference}} @class mutable CompositeComponentDef <: ComponentDef begin comps_dict::OrderedDict{Symbol, AbstractComponentDef} @@ -125,8 +134,6 @@ global const NamespaceElement = Union{ParameterDefReference, VariableDefReferenc external_param_conns::Vector{ExternalParameterConnection} external_params::Dict{Symbol, ModelParameter} # TBD: make key (ComponentPath, Symbol)? - namespace::Dict{Symbol, NamespaceElement} - # Names of external params that the ConnectorComps will use as their :input2 parameters. backups::Vector{Symbol} diff --git a/test/test_defcomposite.jl b/test/test_defcomposite.jl new file mode 100644 index 000000000..a34d7b234 --- /dev/null +++ b/test/test_defcomposite.jl @@ -0,0 +1,68 @@ +module TestDefComposite + +using Test +using Mimi +using MacroTools + +import Mimi: ComponentPath, build + # ComponentId, , DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, + # Binding, ExportsDict, ModelDef, build, time_labels, compdef, find_comp + + +@defcomp Comp1 begin + par_1_1 = Parameter(index=[time]) # external input + var_1_1 = Variable(index=[time]) # computed + foo = Parameter() + + function run_timestep(p, v, d, t) + v.var_1_1[t] = p.par_1_1[t] + end +end + +@defcomp Comp2 begin + par_2_1 = Parameter(index=[time]) # connected to Comp1.var_1_1 + par_2_2 = Parameter(index=[time]) # external input + var_2_1 = Variable(index=[time]) # computed + foo = Parameter() + + function run_timestep(p, v, d, t) + v.var_2_1[t] = p.par_2_1[t] + p.foo * p.par_2_2[t] + end +end + +m = Model() +set_dimension!(m, :time, 2005:2020) + +@defcomposite A begin + component(Comp1) + component(Comp2) + + # foo1 = Comp1.foo # import + # foo2 = Comp2.foo # import + + # linked imports + foo = Comp1.foo, Comp2.foo + + # connections + Comp1.par_1_1 = Comp2.var_2_1 + Comp2.par_2_1 = Comp1.var_1_1 + Comp2.par_2_2 = Comp1.var_1_1 +end + +add_comp!(m, A) + +a = m.md[:A] + +set_param!(a, :Comp1, :foo, 10) +set_param!(a, :Comp2, :foo, 4) # TBD: why does this overwrite the 10 above?? + +build(m) +#run(m) + +end # module + +m = TestDefComposite.m +A = TestDefComposite.A + + +nothing From e6d299fd0678ff276c9aa6a97de7d45066a97ba2 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 20 Jun 2019 08:33:50 -0700 Subject: [PATCH 67/81] WIP -- adding namespace support to components --- src/core/defcomp.jl | 4 +- src/core/defcomposite.jl | 51 ++++++++++++++++--- src/core/defs.jl | 103 +++++++++++++++++++++++--------------- src/core/paths.jl | 15 +++++- src/core/show.jl | 17 ++++++- src/core/types/defs.jl | 26 ++++++++-- test/test_defcomposite.jl | 7 +-- 7 files changed, 164 insertions(+), 59 deletions(-) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index db3ab7ce1..301d12133 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -72,8 +72,8 @@ end # only references to vars in components contained within. function add_variable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) v = VariableDef(name, comp_def.comp_path, datatype, dimensions, description, unit) - comp_def[name] = v # adds to namespace and checks for duplicate comp_def.variables[name] = v + comp_def[name] = v # adds to namespace and checks for duplicate return v end @@ -84,8 +84,8 @@ end function add_parameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, comp_def.comp_path, datatype, dimensions, description, unit, default) - comp_def[name] = p # adds to namespace and checks for duplicate comp_def.parameters[name] = p + comp_def[name] = p # adds to namespace and checks for duplicate dirty!(comp_def) return p end diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index c4b0111ce..42d9d1733 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -111,6 +111,37 @@ function _parse_dotted_symbols(expr) return ComponentPath(syms), var_or_par end +function _ns_params(comp::AbstractComponentDef) + filter(pair -> pair.second isa Vector{ParameterDefReference}, comp.namespace) +end + +function import_params(comp::AbstractCompositeComponentDef) + # nothing to do if there are no sub-components + length(comp.comps_dict) == 0 && return + + # grab the already-imported items from the namespace; create a reverse-lookup map + d = Dict() + for (local_name, param_refs) in _ns_params(comp) + for ref in param_refs + d[(ref.comp_path, ref.name)] = local_name + end + end + + @info "import_params: reverse lookup: $d" + + # Iterate over all sub-components and import all params not already referenced (usually renamed) + for (comp_name, sub_comp) in comp.comps_dict + path = sub_comp.comp_path + @info " path: $path" + for (local_name, param_refs) in _ns_params(sub_comp) + for ref in param_refs + if ! haskey(d, (ref.comp_path, ref.name)) + comp[local_name] = ref # import it + end + end + end + end +end """ defcomposite(cc_name::Symbol, ex::Expr) @@ -211,10 +242,10 @@ macro defcomposite(cc_name, ex) Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) end - for (local_name, var_par_vec) in imports + for (local_name, item) in imports refs = [] - for (src_path, src_name) in var_par_vec + for (src_path, src_name) in item dr = Mimi.DatumReference(src_name, $cc_name, src_path) var_par_ref = (Mimi.is_parameter(dr) ? Mimi.ParameterDefReference(dr) : Mimi.VariableDefReference(dr)) push!(refs, var_par_ref) @@ -224,16 +255,20 @@ macro defcomposite(cc_name, ex) count = length(refs) if count == 1 && refs[1] isa Mimi.VariableDefReference $cc_name[local_name] = refs[1] # store single VariableDefReference; multiples not supported - elseif count > 1 - vars = filter(obj -> obj isa Mimi.VariableDefReference, refs) - if length(vars) > 0 - error("Variables ($vars) must be aliased only individually.") - else - $cc_name[local_name] = Vector{Mimi.ParameterDefReference}(refs) # fix type + else + if count > 1 + vars = filter(obj -> obj isa Mimi.VariableDefReference, refs) + if length(vars) > 0 + error("Variables ($vars) must be aliased only individually.") + end end + + $cc_name[local_name] = Vector{Mimi.ParameterDefReference}(refs) # tweak array type end end + Mimi.import_params($cc_name) + $cc_name end ) diff --git a/src/core/defs.jl b/src/core/defs.jl index 9f2317c0d..583c229d1 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -127,28 +127,28 @@ end function Base.getindex(comp::AbstractComponentDef, key::Symbol) value = comp.namespace[key] + return value + + # value isa AbstractComponentDef && return value - value isa AbstractComponentDef && return value - - # Variables can't be linked (not an array of values). - # If there are linked params, all have the same value, use first. - # If not linked, params are still stored as vector of length 1. - ref = (value isa Vector ? value[1] : value) + # # Variables can't be linked (not an array of values). + # # If there are linked params, all have the same value, use first. + # # If not linked, params are still stored as vector of length 1. + # ref = (value isa Vector ? value[1] : value) - # follow reference to access value of parameter - obj = find_comp(ref.root, ref.comp_path) - obj === nothing && error("Failed to find referenced parameter: $ref") + # # follow reference to access value of parameter + # obj = find_comp(ref.root, ref.comp_path) + # obj === nothing && error("Failed to find referenced parameter: $ref") - return obj[ref.name] + # return obj[ref.name] end - -function Base.setindex!(comp::AbstractComponentDef, value::T, key::Symbol) where T <: NamespaceElement - # For leaf components, setindex! can store only vars and vectors of pars - if comp isa ComponentDef && T isa AbstractComponentDef - error("Cannot store components in leaf component $(comp.comp_path)") - end - +function _save_to_namespace(comp::AbstractComponentDef, + key::Symbol, value::Union{NamespaceElement, ParameterDefReference}) + # convert single param def ref to a vector of one for consistency + value = (value isa ParameterDefReference ? [value] : value) + T = typeof(value) + # Allow replacement of existing values for a key only with items of the same type. if haskey(comp, key) elt_type = typeof(comp[key]) @@ -156,7 +156,22 @@ function Base.setindex!(comp::AbstractComponentDef, value::T, key::Symbol) where end comp.namespace[key] = value +end + +# Leaf components store ParameterDefReference or VariableDefReference instances in the namespace +function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol) + ref = datum_reference(comp, value.name) + _save_to_namespace(comp, key, ref) + return value +end + +function Base.setindex!(comp::AbstractCompositeComponentDef, ref::ParameterDefReference, key::Symbol) + _save_to_namespace(comp, key, ref) +end +function Base.setindex!(comp::AbstractCompositeComponentDef, value::NamespaceElement, key::Symbol) + _save_to_namespace(comp, key, value) + # Also store in comps_dict, if value is a component # TBD: drop comps_dict in favor of namespace for both purposes if value isa AbstractComponentDef @@ -905,7 +920,7 @@ Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; - exports=nothing, + exports=nothing, # TBD: deprecated first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -919,32 +934,34 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot set first or last period when adding a composite component: $(comp_def.comp_id)") end + # + # deprecated + # # If not specified, export all var/pars. Caller can pass empty list to export nothing. # TBD: actually, might work better to export nothing unless declared as such. - if exports === nothing - exports = [] - # exports = [variable_names(comp_def)..., parameter_names(comp_def)...] - end - - for item in exports - if item isa Pair - (name, export_name) = item - elseif item isa Symbol - name = export_name = item - else - error("Exports argument to add_comp! must be pair or symbol, got: $item") - end - - # TBD: should this just add to obj.variables / obj.parameters dicts? - # Those dicts hold ParameterDef / VariableDef, which we want to reference, not - # duplicate when building instances. One approach would be for the build step - # to create a dict on objectid(x) to store/find the generated var/param. - if haskey(obj.exports, export_name) - error("Exports may not include a duplicate name ($export_name)") - end + # if exports === nothing + # exports = [] + # end - obj.exports[export_name] = _find_var_par(obj, comp_def, comp_name, name) - end + # for item in exports + # if item isa Pair + # (name, export_name) = item + # elseif item isa Symbol + # name = export_name = item + # else + # error("Exports argument to add_comp! must be pair or symbol, got: $item") + # end + + # # TBD: should this just add to obj.variables / obj.parameters dicts? + # # Those dicts hold ParameterDef / VariableDef, which we want to reference, not + # # duplicate when building instances. One approach would be for the build step + # # to create a dict on objectid(x) to store/find the generated var/param. + # if haskey(obj.exports, export_name) + # error("Exports may not include a duplicate name ($export_name)") + # end + + # obj.exports[export_name] = _find_var_par(obj, comp_def, comp_name, name) + # end # Check if component being added already exists if has_comp(obj, comp_name) @@ -993,6 +1010,10 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone end end + # Handle special case of adding to a ModelDef, which isn't done with @defcomposite, + # which calls import_params after adding all components and explicit imports. + obj isa AbstractModelDef && import_params(obj) + # Return the comp since it's a copy of what was passed in return comp_def end diff --git a/src/core/paths.jl b/src/core/paths.jl index 8e27344e9..ad838207f 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -21,12 +21,25 @@ end comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) Set the ComponentPath of a child object to extend the path of its composite parent. -For composites, also update the component paths for all internal connections. +For composites, also update the component paths for all internal connections, and +for all DatumReferences in the namespace. For leaf components, update the ComponentPath for ParameterDefs and VariableDefs. """ function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) child.comp_path = path = ComponentPath(parent.comp_path, child.name) + # First, fix up child's namespace objs. We later recurse down the hierarchy. + ns = child.namespace + root = get_root(parent) + + for (name, ref) in ns + if ref isa AbstractDatumReference + T = typeof(ref) + ns[name] = new_ref = T(ref.name, root, path) + @info "old ref: $ref, new: $new_ref" + end + end + # recursively reset all comp_paths if is_composite(child) diff --git a/src/core/show.jl b/src/core/show.jl index 0ac13d7ca..552896958 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -153,11 +153,14 @@ function show(io::IO, obj::AbstractMimiType) end function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) - # Don't print parent or root since these create circular references print(io, nameof(typeof(obj)), " id:", objectid(obj)) fields = fieldnames(typeof(obj)) + # skip the 'namespace' field since it's redundant + fields = [f for f in fields if f != :namespace] + + # Don't print parent or root since these create circular references for field in (:parent, :root) pos = findfirst(x -> x == field, fields) if pos !== nothing @@ -171,6 +174,18 @@ function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) io = indent(io) _show_fields(io, obj, fields) + + if obj isa AbstractComponentDef + # print an abbreviated namespace + print(io, "\n") + print_indented(io, "namespace:") + io = indent(io) + for (name, item) in obj.namespace + print(io, "\n") + print_indented(io, name, ": ") + show(io, typeof(item)) + end + end end function show(io::IO, obj::ModelInstance) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 277aaa461..3a587369f 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -85,6 +85,7 @@ end end end +ns(obj::AbstractComponentDef) = obj.namespace comp_id(obj::AbstractComponentDef) = obj.comp_id pathof(obj::AbstractComponentDef) = obj.comp_path dim_dict(obj::AbstractComponentDef) = obj.dim_dict @@ -115,9 +116,28 @@ end @class VariableDefReference <: DatumReference -# "Upgrade" a DatumReference to one of the specific types -ParameterDefReference(dr::DatumReference) = ParameterDefReference(dr.name, dr.root, dr.comp_path) -VariableDefReference(dr::DatumReference) = VariableDefReference(dr.name, dr.root, dr.comp_path) +function datum_reference(comp::ComponentDef, datum_name::Symbol) + root = get_root(comp) + + # @info "compid: $(comp.comp_id)" + # @info "datum_reference: comp path: $(printable(comp.comp_path)) parent: $(printable(comp.parent))" + + if has_variable(comp, datum_name) + var = comp.variables[datum_name] + path = @or(var.comp_path, ComponentPath(comp.name)) + # @info " var path: $path)" + return VariableDefReference(datum_name, root, path) + end + + if has_parameter(comp, datum_name) + par = comp.parameters[datum_name] + path = @or(par.comp_path, ComponentPath(comp.name)) + # @info " par path: $path)" + return ParameterDefReference(datum_name, root, path) + end + + error("Component $(comp.comp_id) does not have a data item named :$datum_name") +end # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} diff --git a/test/test_defcomposite.jl b/test/test_defcomposite.jl index a34d7b234..62ba51c83 100644 --- a/test/test_defcomposite.jl +++ b/test/test_defcomposite.jl @@ -37,8 +37,9 @@ set_dimension!(m, :time, 2005:2020) component(Comp1) component(Comp2) - # foo1 = Comp1.foo # import - # foo2 = Comp2.foo # import + # imports + bar = Comp1.par_1_1 + foo2 = Comp2.foo # linked imports foo = Comp1.foo, Comp2.foo @@ -63,6 +64,6 @@ end # module m = TestDefComposite.m A = TestDefComposite.A - +md = m.md nothing From 07e9f465ae8ee8f9cc8c9f34cc7d9ad3edf41d79 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 5 Aug 2019 12:29:00 -0700 Subject: [PATCH 68/81] - Add support for component namespaces combining params and vars - Move dimensions-related code to separate file - Replace numcomponents() with length() --- src/core/build.jl | 1 - src/core/defcomposite.jl | 16 +- src/core/defs.jl | 255 +++++------------- src/core/dimensions.jl | 81 ++++++ src/core/model.jl | 4 +- src/core/show.jl | 2 +- src/core/types/defs.jl | 8 +- test/test_components.jl | 6 +- test/test_model_structure.jl | 2 +- test/test_model_structure_variabletimestep.jl | 2 +- 10 files changed, 168 insertions(+), 209 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 92b7da68f..a8e31d6f1 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -218,7 +218,6 @@ end function _build(md::ModelDef) # @info "_build(md)" - # _propagate_exports(md) add_connector_comps(md) # check if all parameters are set diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 42d9d1733..04463dd7a 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -111,18 +111,14 @@ function _parse_dotted_symbols(expr) return ComponentPath(syms), var_or_par end -function _ns_params(comp::AbstractComponentDef) - filter(pair -> pair.second isa Vector{ParameterDefReference}, comp.namespace) -end - function import_params(comp::AbstractCompositeComponentDef) # nothing to do if there are no sub-components - length(comp.comps_dict) == 0 && return + length(comp) == 0 && return # grab the already-imported items from the namespace; create a reverse-lookup map d = Dict() - for (local_name, param_refs) in _ns_params(comp) - for ref in param_refs + for (local_name, prefs) in param_refs(comp) + for ref in prefs d[(ref.comp_path, ref.name)] = local_name end end @@ -130,11 +126,11 @@ function import_params(comp::AbstractCompositeComponentDef) @info "import_params: reverse lookup: $d" # Iterate over all sub-components and import all params not already referenced (usually renamed) - for (comp_name, sub_comp) in comp.comps_dict + for (comp_name, sub_comp) in components(comp) path = sub_comp.comp_path @info " path: $path" - for (local_name, param_refs) in _ns_params(sub_comp) - for ref in param_refs + for (local_name, prefs) in param_refs(sub_comp) + for ref in prefs if ! haskey(d, (ref.comp_path, ref.name)) comp[local_name] = ref # import it end diff --git a/src/core/defs.jl b/src/core/defs.jl index 583c229d1..4b978bd10 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,3 +1,21 @@ +""" + istype(T::DataType) + +Return an anonymous func that can be used to filter a dict by data type of values. +Example: `filter(istype(AbstractComponentDef), obj.namespace)` +""" +istype(T::DataType) = (pair -> pair.second isa T) + +# Namespace filter functions; these return dicts of values for the given type. +# N.B. only composites hold comps in the namespace. +components(obj::AbstractCompositeComponentDef) = filter(istype(AbstractComponentDef), obj.namespace) + +var_refs(obj::AbstractComponentDef) = filter(istype(VariableDefReference), obj.namespace) +param_refs(obj::AbstractComponentDef) = filter(istype(Vector{ParameterDefReference}), obj.namespace) + +Base.length(obj::AbstractComponentDef) = 0 # no sub-components +Base.length(obj::AbstractCompositeComponentDef) = length(components(obj)) + function find_module(path::NTuple{N, Symbol} where N) m = Main for name in path @@ -26,12 +44,12 @@ compdef(dr::AbstractDatumReference) = find_comp(dr.root, dr.comp_path) compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) -compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.comps_dict[comp_name] +compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.namespace[comp_name] -has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.comps_dict, comp_name) +has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.namespace, comp_name) -compdefs(c::AbstractCompositeComponentDef) = values(c.comps_dict) -compkeys(c::AbstractCompositeComponentDef) = keys(c.comps_dict) +compdefs(c::AbstractCompositeComponentDef) = values(components(c)) +compkeys(c::AbstractCompositeComponentDef) = keys(components(c)) # Allows method to be called harmlessly on leaf component defs, which simplifies recursive funcs. compdefs(c::ComponentDef) = [] @@ -87,10 +105,6 @@ last_period(root::AbstractCompositeComponentDef, comp::AbstractComponentDef) = find_first_period(comp_def::AbstractComponentDef) = @or(first_period(comp_def), first_period(get_root(comp_def))) find_last_period(comp_def::AbstractComponentDef) = @or(last_period(comp_def), last_period(get_root(comp_def))) -# TBD: should be numcomps() -numcomponents(obj::AbstractComponentDef) = 0 # no sub-components -numcomponents(obj::AbstractCompositeComponentDef) = length(obj.comps_dict) - """ delete!(obj::AbstractCompositeComponentDef, component::Symbol) @@ -102,17 +116,19 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) end comp_def = compdef(ccd, comp_name) - delete!(ccd.comps_dict, comp_name) + delete!(ccd.namespace, comp_name) # Remove references to the deleted comp + # TBD: make this work off namespace instead comp_path = comp_def.comp_path exports = ccd.exports - for (key, dr) in exports - if dr.comp_path == comp_path - delete!(exports, key) - end - end + # deprecated + # for (key, dr) in exports + # if dr.comp_path == comp_path + # delete!(exports, key) + # end + # end # TBD: find and delete external_params associated with deleted component? Currently no record of this. @@ -143,15 +159,11 @@ function Base.getindex(comp::AbstractComponentDef, key::Symbol) # return obj[ref.name] end -function _save_to_namespace(comp::AbstractComponentDef, - key::Symbol, value::Union{NamespaceElement, ParameterDefReference}) - # convert single param def ref to a vector of one for consistency - value = (value isa ParameterDefReference ? [value] : value) - T = typeof(value) - +function _save_to_namespace(comp::AbstractComponentDef, key::Symbol, value::NamespaceElement) # Allow replacement of existing values for a key only with items of the same type. if haskey(comp, key) elt_type = typeof(comp[key]) + T = typeof(value) elt_type == T || error("Cannot replace item $key, type $elt_type, with object type $T in component $(comp.comp_path).") end @@ -165,49 +177,20 @@ function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol return value end -function Base.setindex!(comp::AbstractCompositeComponentDef, ref::ParameterDefReference, key::Symbol) - _save_to_namespace(comp, key, ref) -end function Base.setindex!(comp::AbstractCompositeComponentDef, value::NamespaceElement, key::Symbol) _save_to_namespace(comp, key, value) - - # Also store in comps_dict, if value is a component - # TBD: drop comps_dict in favor of namespace for both purposes - if value isa AbstractComponentDef - comp.comps_dict[key] = value - end +end - return value +# Handle case of single ParameterDefReference by converting to 1-element array +function Base.setindex!(comp::AbstractCompositeComponentDef, ref::ParameterDefReference, key::Symbol) + setindex!(comp, [ref], key) end # # Dimensions # -function add_dimension!(comp::AbstractComponentDef, name) - # generally, we add dimension name with nothing instead of a Dimension instance, - # but in the case of an Int name, we create the "anonymous" dimension on the fly. - dim = (name isa Int) ? Dimension(name) : nothing - comp.dim_dict[Symbol(name)] = dim # TBD: test this -end - -# Note that this operates on the registered comp, not one added to a composite -add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) - -function dim_names(ccd::AbstractCompositeComponentDef) - dims = OrderedSet{Symbol}() # use a set to eliminate duplicates - for cd in compdefs(ccd) - union!(dims, keys(dim_dict(cd))) # TBD: test this - end - - return collect(dims) -end - -dim_names(comp_def::AbstractComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) - -dim_count(def::AbstractDatumDef) = length(dim_names(def)) - step_size(values::Vector{Int}) = (length(values) > 1 ? values[2] - values[1] : 1) # @@ -260,34 +243,6 @@ function datum_size(obj::AbstractCompositeComponentDef, comp_def::ComponentDef, return datum_size end -######## -# Should these all be defined for leaf ComponentDefs? What is the time (or other) dimension for -# a leaf component? Is time always determined from ModelDef? What about other dimensions that may -# be defined differently in a component? -######## - -# Symbols are added to the dim_dict in @defcomp (with value of nothing), but are set later using set_dimension! -has_dim(obj::AbstractCompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) - -isuniform(obj::AbstractCompositeComponentDef) = obj.is_uniform - -set_uniform!(obj::AbstractCompositeComponentDef, value::Bool) = (obj.is_uniform = value) - -dimension(obj::AbstractCompositeComponentDef, name::Symbol) = obj.dim_dict[name] - -dim_names(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [dimension(obj, dim) for dim in dims] - -dim_count_dict(obj::AbstractCompositeComponentDef) = Dict([name => length(value) for (name, value) in dim_dict(obj)]) - -# deprecated? -#dim_key_dict(obj::AbstractCompositeComponentDef) = Dict([name => collect(keys(dim)) for (name, dim) in dimensions(obj)]) - -dim_counts(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(obj, dims)] -dim_count(obj::AbstractCompositeComponentDef, name::Symbol) = length(dimension(obj, name)) - -dim_keys(obj::AbstractCompositeComponentDef, name::Symbol) = collect(keys(dimension(obj, name))) -dim_values(obj::AbstractCompositeComponentDef, name::Symbol) = collect(values(dimension(obj, name))) - """ _check_run_period(obj::AbstractComponentDef, first, last) @@ -347,60 +302,15 @@ function _set_run_period!(obj::AbstractComponentDef, first, last) nothing end -""" - set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) - -Set the values of `ccd` dimension `name` to integers 1 through `count`, if `keys` is -an integer; or to the values in the vector or range if `keys` is either of those types. -""" -function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) - redefined = has_dim(ccd, name) - # if redefined - # @warn "Redefining dimension :$name" - # end - - dim = Dimension(keys) - - if name == :time - _set_run_period!(ccd, keys[1], keys[end]) - propagate_time(ccd, dim) - set_uniform!(ccd, isuniform(keys)) - end - - return set_dimension!(ccd, name, dim) -end - -function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) - dirty!(obj) - obj.dim_dict[name] = dim - - if name == :time - for subcomp in compdefs(obj) - set_dimension!(subcomp, :time, dim) - end - end - return dim -end - # helper functions used to determine if the provided time values are # a uniform range. -function all_equal(values) - return all(map(val -> val == values[1], values[2:end])) -end +all_equal(values) = all(map(val -> val == values[1], values[2:end])) -function isuniform(values) - if length(values) == 0 - return false - else - return all_equal(diff(collect(values))) - end -end +isuniform(values) = (length(values) == 0 ? false : all_equal(diff(collect(values)))) # needed when time dimension is defined using a single integer -function isuniform(values::Int) - return true -end +isuniform(values::Int) = true # # Data references @@ -449,6 +359,7 @@ Return a list of the parameter definitions for `comp_def`. """ parameters(obj::AbstractComponentDef) = values(obj.parameters) +# TBD: deprecated function parameters(ccd::AbstractCompositeComponentDef; reset::Bool=false) pars = ccd.parameters @@ -481,19 +392,21 @@ parameter(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Sym parameter(dr::ParameterDefReference) = parameter(compdef(dr), nameof(dr)) function _parameter(obj::AbstractComponentDef, name::Symbol) - try + if haskey(obj.parameters, name) return obj.parameters[name] - catch - error("Parameter $name was not found in component $(nameof(obj))") end + + error("Parameter $name was not found in component $(nameof(obj))") end parameter(obj::ComponentDef, name::Symbol) = _parameter(obj, name) +# TBD: modify to use namespace function parameter(obj::AbstractCompositeComponentDef, name::Symbol) - if ! is_exported(obj, name) - error("Parameter $name is not exported by composite component $(obj.comp_path)") + if ! haskey(obj.namespace, name) + error("Item $name is not present in composite component $(obj.comp_path)") end + _parameter(obj, name) end @@ -697,12 +610,12 @@ end variable(obj::ComponentDef, name::Symbol) = _variable(obj, name) function variable(obj::AbstractCompositeComponentDef, name::Symbol) - # TBD test this can be dropped if we maintain vars/pars dynamically _collect_data_refs(obj) - if ! is_exported(obj, name) - error("Variable $name is not exported by composite component $(obj.comp_path)") - end + if ! haskey(obj.namespace, name) + error("Item $name is not present in composite component $(obj.comp_path)") + end + _variable(obj, name) end @@ -734,16 +647,16 @@ function variable_unit(obj::AbstractCompositeComponentDef, comp_path::ComponentP return var.unit end -function variable_dimensions(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) - var = variable(obj, comp_path, var_name) - return dim_names(var) -end - function variable_unit(obj::AbstractComponentDef, name::Symbol) var = variable(obj, name) return var.unit end +function variable_dimensions(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) + var = variable(obj, comp_path, var_name) + return dim_names(var) +end + function variable_dimensions(obj::AbstractComponentDef, name::Symbol) var = variable(obj, name) return dim_names(var) @@ -779,14 +692,14 @@ function _add_anonymous_dims!(obj::AbstractCompositeComponentDef, comp_def::Abst end end -function comps_dict!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) - for key in keys(obj.comps_dict) - delete!(obj.namespace, key) +function _set_comps!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) + for key in keys(components(obj)) + delete!(obj, key) end - empty!(obj.comps_dict) + # add comps to namespace for (key, value) in comps - obj[key] = value # adds to both comps_dict and namespace + obj[key] = value end dirty!(obj) @@ -811,7 +724,7 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom comp_name = nameof(comp_def) if before === nothing && after === nothing - obj[comp_name] = comp_def # add to comps_dict and to namespace + obj[comp_name] = comp_def # add to namespace else new_comps = OrderedDict{Symbol, AbstractComponentDef}() @@ -820,7 +733,7 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom error("Component to add before ($before) does not exist") end - for (k, v) in obj.comps_dict + for (k, v) in components(obj) if k == before new_comps[comp_name] = comp_def end @@ -832,7 +745,7 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom error("Component to add before ($before) does not exist") end - for (k, v) in obj.comps_dict + for (k, v) in components(obj) new_comps[k] = v if k == after new_comps[comp_name] = comp_def @@ -840,7 +753,7 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom end end - comps_dict!(obj, new_comps) + _set_comps!(obj, new_comps) end comp_path!(comp_def, obj) @@ -870,6 +783,7 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract # @info "comp path: $path, datum_name: $datum_name" + # TBD: should be obviated by namespace if is_composite(comp_def) # find and cache locally exported vars & pars variables(comp_def) @@ -934,39 +848,8 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot set first or last period when adding a composite component: $(comp_def.comp_id)") end - # - # deprecated - # - # If not specified, export all var/pars. Caller can pass empty list to export nothing. - # TBD: actually, might work better to export nothing unless declared as such. - # if exports === nothing - # exports = [] - # end - - # for item in exports - # if item isa Pair - # (name, export_name) = item - # elseif item isa Symbol - # name = export_name = item - # else - # error("Exports argument to add_comp! must be pair or symbol, got: $item") - # end - - # # TBD: should this just add to obj.variables / obj.parameters dicts? - # # Those dicts hold ParameterDef / VariableDef, which we want to reference, not - # # duplicate when building instances. One approach would be for the build step - # # to create a dict on objectid(x) to store/find the generated var/param. - # if haskey(obj.exports, export_name) - # error("Exports may not include a duplicate name ($export_name)") - # end - - # obj.exports[export_name] = _find_var_par(obj, comp_def, comp_name, name) - # end - # Check if component being added already exists - if has_comp(obj, comp_name) - error("Cannot add two components of the same name ($comp_name)") - end + has_comp(obj, comp_name) && error("Cannot add two components of the same name ($comp_name)") # check time constraints if the time dimension has been set if has_dim(obj, :time) @@ -1134,8 +1017,8 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end filter!(epc -> !(epc in remove), external_param_conns(obj)) - # Delete the old component from comps_dict, leaving the existing parameter connections - delete!(obj.comps_dict, comp_name) + # Delete the old component from composite, leaving the existing parameter connections + delete!(obj.namespace, comp_name) else # Delete the old component and all its internal and external parameter connections delete!(obj, comp_name) diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index 057404eb3..e008e6db2 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -59,3 +59,84 @@ end # Support dim[[2010, 2020, 2030]], dim[(:foo, :bar, :baz)], and dim[2010:2050] Base.getindex(dim::RangeDimension, keys::Union{Vector{Int}, Tuple, AbstractRange}) = [get(dim, key, 0) for key in keys] + +# Symbols are added to the dim_dict in @defcomp (with value of nothing), but are set later using set_dimension! +has_dim(obj::AbstractCompositeComponentDef, name::Symbol) = (haskey(obj.dim_dict, name) && obj.dim_dict[name] !== nothing) + +isuniform(obj::AbstractCompositeComponentDef) = obj.is_uniform + +set_uniform!(obj::AbstractCompositeComponentDef, value::Bool) = (obj.is_uniform = value) + +dimension(obj::AbstractCompositeComponentDef, name::Symbol) = obj.dim_dict[name] + +dim_names(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [dimension(obj, dim) for dim in dims] + +dim_count_dict(obj::AbstractCompositeComponentDef) = Dict([name => length(value) for (name, value) in dim_dict(obj)]) + +# deprecated? +#dim_key_dict(obj::AbstractCompositeComponentDef) = Dict([name => collect(keys(dim)) for (name, dim) in dimensions(obj)]) + +dim_counts(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(obj, dims)] +dim_count(obj::AbstractCompositeComponentDef, name::Symbol) = length(dimension(obj, name)) + +dim_keys(obj::AbstractCompositeComponentDef, name::Symbol) = collect(keys(dimension(obj, name))) +dim_values(obj::AbstractCompositeComponentDef, name::Symbol) = collect(values(dimension(obj, name))) + +""" + set_dimension!(ccd::CompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + +Set the values of `ccd` dimension `name` to integers 1 through `count`, if `keys` is +an integer; or to the values in the vector or range if `keys` is either of those types. +""" +function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + redefined = has_dim(ccd, name) + # if redefined + # @warn "Redefining dimension :$name" + # end + + dim = Dimension(keys) + + if name == :time + _set_run_period!(ccd, keys[1], keys[end]) + propagate_time(ccd, dim) + set_uniform!(ccd, isuniform(keys)) + end + + return set_dimension!(ccd, name, dim) +end + +function set_dimension!(obj::AbstractComponentDef, name::Symbol, dim::Dimension) + dirty!(obj) + obj.dim_dict[name] = dim + + if name == :time + for subcomp in compdefs(obj) + set_dimension!(subcomp, :time, dim) + end + end + return dim +end + +function add_dimension!(comp::AbstractComponentDef, name) + # generally, we add dimension name with nothing instead of a Dimension instance, + # but in the case of an Int name, we create the "anonymous" dimension on the fly. + dim = (name isa Int) ? Dimension(name) : nothing + comp.dim_dict[Symbol(name)] = dim # TBD: test this +end + +# Note that this operates on the registered comp, not one added to a composite +add_dimension!(comp_id::ComponentId, name) = add_dimension!(compdef(comp_id), name) + +function dim_names(ccd::AbstractCompositeComponentDef) + dims = OrderedSet{Symbol}() # use a set to eliminate duplicates + for cd in compdefs(ccd) + union!(dims, keys(dim_dict(cd))) # TBD: test this + end + + return collect(dims) +end + +dim_names(comp_def::AbstractComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) + +dim_count(def::AbstractDatumDef) = length(dim_names(def)) + diff --git a/src/core/model.jl b/src/core/model.jl index aae0faa9a..07f89a08b 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -175,7 +175,7 @@ Return an iterator on the components in a model's model instance. @delegate compdef(m::Model, comp_name::Symbol) => md -@delegate numcomponents(m::Model) => md +@delegate Base.length(m::Model) => md @delegate first_and_step(m::Model) => md @@ -353,7 +353,7 @@ Run model `m` once. """ function Base.run(m::Model; ntimesteps::Int=typemax(Int), rebuild::Bool=false, dim_keys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) - if numcomponents(m) == 0 + if length(m) == 0 error("Cannot run a model with no components.") end diff --git a/src/core/show.jl b/src/core/show.jl index 552896958..3e4539bd5 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -215,7 +215,7 @@ function _show(io::IO, obj::Model, which::Symbol) println(io, " Module: $(md.comp_id.module_path)") println(io, " Components:") - for comp in values(md.comps_dict) + for comp in values(components(md)) println(io, " $(comp.comp_id)") end diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 3a587369f..3f1b44a4f 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -40,7 +40,7 @@ end variables::OrderedDict{Symbol, VariableDef} parameters::OrderedDict{Symbol, ParameterDef} dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} - namespace::Dict{Symbol, Any} + namespace::OrderedDict{Symbol, Any} first::Union{Nothing, Int} last::Union{Nothing, Int} is_uniform::Bool @@ -71,7 +71,7 @@ end self.variables = OrderedDict{Symbol, VariableDef}() self.parameters = OrderedDict{Symbol, ParameterDef}() self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() - self.namespace = Dict{Symbol, Any}() + self.namespace = OrderedDict{Symbol, Any}() self.first = self.last = nothing self.is_uniform = true self.parent = nothing @@ -146,7 +146,7 @@ global const ExportsDict = Dict{Symbol, AbstractDatumReference} global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference, Vector{ParameterDefReference}} @class mutable CompositeComponentDef <: ComponentDef begin - comps_dict::OrderedDict{Symbol, AbstractComponentDef} + #comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Binding} exports::ExportsDict @@ -169,7 +169,7 @@ global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference ComponentDef(self, comp_id) # call superclass' initializer self.comp_path = ComponentPath(self.name) - self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() + # self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() self.bindings = Vector{Binding}() self.exports = ExportsDict() self.internal_param_conns = Vector{InternalParameterConnection}() diff --git a/test/test_components.jl b/test/test_components.jl index 709879f6f..e7856b96e 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -11,7 +11,7 @@ my_model = Model() # Try running model with no components @test length(compdefs(my_model)) == 0 -@test numcomponents(my_model) == 0 +@test length(my_model) == 0 @test_throws ErrorException run(my_model) # Now add several components to the module @@ -85,9 +85,9 @@ comps = collect(compdefs(my_model)) @test compmodule(testcomp3) == :TestComponents @test compname(testcomp3) == :testcomp3 -@test numcomponents(my_model) == 3 +@test length(my_model) == 3 add_comp!(my_model, testcomp3, :testcomp3_v2) -@test numcomponents(my_model) == 4 +@test length(my_model) == 4 #------------------------------------------------------------------------------ diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 88b9414fa..816a6c76d 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -65,7 +65,7 @@ connect_param!(m, :C => :parC, :B => :varB) @test_throws ErrorException add_comp!(m, C, after=:A, before=:B) -@test numcomponents(m.md) == 3 +@test length(m.md) == 3 @test length(internal_param_conns(m)) == 2 c = compdef(m, :C) diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 76a2ee410..33714b470 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -84,7 +84,7 @@ connect_param!(m, :C => :parC, :B => :varB) @test_throws ErrorException add_comp!(m, C, after=:A, before=:B) -@test numcomponents(m.md) == 3 +@test length(m.md) == 3 @test length(internal_param_conns(m)) == 2 c = compdef(m, :C) From a1688e413239d01187875dd9af0616c54d75df88 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 8 Aug 2019 15:28:14 -0700 Subject: [PATCH 69/81] WIP: various adjustments to pass tests. --- src/core/build.jl | 40 +++++------ src/core/connections.jl | 2 +- src/core/defcomp.jl | 4 +- src/core/defcomposite.jl | 20 +++--- src/core/defmodel.jl | 1 - src/core/defs.jl | 21 +++--- src/core/instances.jl | 22 +++--- src/core/model.jl | 40 ----------- src/core/paths.jl | 8 +-- src/core/time_arrays.jl | 68 ++++++++++--------- src/core/types/defs.jl | 9 +-- src/core/types/instances.jl | 62 ++++++++++------- test/test_components.jl | 14 ++-- test/test_composite.jl | 4 +- test/test_getdataframe.jl | 2 +- test/test_model_structure.jl | 4 +- test/test_model_structure_variabletimestep.jl | 8 +-- 17 files changed, 153 insertions(+), 176 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index f1e6124d9..24bb80a73 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -11,7 +11,7 @@ function _instance_datatype(md::ModelDef, def::AbstractDatumDef) if num_dims == 0 T = ScalarModelParameter{dtype} - elseif ti == nothing # there's no time dimension + elseif ti === nothing # there's no time dimension T = Array{dtype, num_dims} else @@ -81,20 +81,22 @@ function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dic names = Symbol[] values = Any[] - for (name, dr) in comp_def.exports - root = dr.root === nothing ? nothing : dr.root.comp_id - # @info "dr.root: $(printable(root)), comp_path: $(printable(dr.comp_path))" - if is_variable(dr) - obj = var_dict[dr.comp_path] - value = getproperty(obj, nameof(dr)) - push!(names, name) - push!(values, value) - end - end + # for (name, dr) in comp_def.exports + # root = dr.root === nothing ? nothing : dr.root.comp_id + # # @info "dr.root: $(printable(root)), comp_path: $(printable(dr.comp_path))" + # if is_variable(dr) + # obj = var_dict[dr.comp_path] + # value = getproperty(obj, nameof(dr)) + # push!(names, name) + # push!(values, value) + # end + # end types = DataType[typeof(val) for val in values] paths = repeat(Any[comp_def.comp_path], length(names)) - return ComponentInstanceVariables(names, types, values, paths) + ci_vars = ComponentInstanceVariables(names, types, values, paths) + # @info "ci_vars: $ci_vars"] + return ci_vars end function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) @@ -103,13 +105,13 @@ function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dic # @info "_combine_exported_pars: $(comp_def.exports)" - for (name, dr) in comp_def.exports - if is_parameter(dr) - value = par_dict[(dr.comp_path, dr.name)] - push!(names, name) - push!(values, value) - end - end + # for (name, dr) in comp_def.exports + # if is_parameter(dr) + # value = par_dict[(dr.comp_path, dr.name)] + # push!(names, name) + # push!(values, value) + # end + # end paths = repeat(Any[comp_def.comp_path], length(names)) types = DataType[typeof(val) for val in values] diff --git a/src/core/connections.jl b/src/core/connections.jl index c012a7c62..f5c94c038 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -596,7 +596,7 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) internal_conns = filter(x -> x.dst_comp_path == comp_path, conns) need_conn_comps = filter(x -> x.backup !== nothing, internal_conns) - # println("Need connectors comps: $need_conn_comps") + isempty(need_conn_comps) || @info "Need connectors comps: $need_conn_comps" for (i, conn) in enumerate(need_conn_comps) add_backup!(obj, conn.backup) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 9ac3d779e..6e993b711 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -3,8 +3,8 @@ # using MacroTools -# Store a list of built-in components so we can suppress messages about creating them -# TBD: and (later) suppress their return in the list of components at the user level. +# Store a list of built-in components so we can suppress messages about creating them. +# TBD: suppress returning these in the list of components at the user level. const global built_in_comps = (:adder, :ConnectorCompVector, :ConnectorCompMatrix) is_builtin(comp_name) = comp_name in built_in_comps diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 04463dd7a..f57af5980 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -19,7 +19,7 @@ function _collect_exports(exprs) push!(exports, name => @or(expname, name)) else error("Elements of exports list must Symbols or Pair{Symbol, Symbol}, got $expr") - end + end end # @info "returning $exports" @@ -38,7 +38,7 @@ function _collect_bindings(exprs) push!(bindings, name => val) else error("Elements of bindings list must Pair{Symbol, Symbol} or Pair{Symbol, Number or Array of Number} got $expr") - end + end end # @info "returning $bindings" @@ -123,12 +123,12 @@ function import_params(comp::AbstractCompositeComponentDef) end end - @info "import_params: reverse lookup: $d" + #@info "import_params: reverse lookup: $d" # Iterate over all sub-components and import all params not already referenced (usually renamed) for (comp_name, sub_comp) in components(comp) path = sub_comp.comp_path - @info " path: $path" + #@info " path: $path" for (local_name, prefs) in param_refs(sub_comp) for ref in prefs if ! haskey(d, (ref.comp_path, ref.name)) @@ -151,7 +151,7 @@ calling signature for `component()` processed herein is: bindings=[list Pair{Symbol, Symbol or Number or Array of Numbers}]) In this macro, the vector of symbols to export is expressed without the `:`, e.g., -`exports=[var_1, var_2 => export_name, param_1])`. The names must be variable or +`exports=[var_1, var_2 => export_name, param_1])`. The names must be variable or parameter names exported to the composite component being added by its sub-components. Bindings are expressed as a vector of `Pair` objects, where the first element of the @@ -168,7 +168,7 @@ associated method on each. """ macro defcomposite(cc_name, ex) # @info "defining composite $cc_name in module $(fullname(__module__))" - + @capture(ex, elements__) comps = SubComponent[] imports = [] @@ -201,7 +201,7 @@ macro defcomposite(cc_name, ex) var_par = right.head == :tuple ? _parse_dotted_symbols.(right.args) : [_parse_dotted_symbols(right)] push!(imports, (left, var_par)) @info "import as $left = $var_par" - + # note that `comp_Symbol.name_Symbol` failed; bug in MacroTools? elseif @capture(left, comp_.name_) && comp isa Symbol && name isa Symbol # simple connection case src = _parse_dotted_symbols(right) @@ -231,13 +231,13 @@ macro defcomposite(cc_name, ex) let conns = $conns, imports = $imports cc_id = Mimi.ComponentId($__module__, $(QuoteNode(cc_name))) - + global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, $__module__) for ((dst_path, dst_name), (src_path, src_name)) in conns Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) end - + for (local_name, item) in imports refs = [] @@ -246,7 +246,7 @@ macro defcomposite(cc_name, ex) var_par_ref = (Mimi.is_parameter(dr) ? Mimi.ParameterDefReference(dr) : Mimi.VariableDefReference(dr)) push!(refs, var_par_ref) end - + # we allow linking parameters, but not variables. count = length(refs) if count == 1 && refs[1] isa Mimi.VariableDefReference diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index 86913c803..b09881dda 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -47,7 +47,6 @@ macro defmodel(model_name, ex) @capture(ex, elements__) # @__MODULE__ is evaluated in calling module when macro is interpreted - # TBD: simplify using __module__ ? result = :( let calling_module = @__MODULE__, comp_mod_name = nothing global $model_name = Model() diff --git a/src/core/defs.jl b/src/core/defs.jl index 3194b37f9..8c1a3cce6 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -44,12 +44,11 @@ compdef(dr::AbstractDatumReference) = find_comp(dr.root, dr.comp_path) compdef(obj::AbstractCompositeComponentDef, path::ComponentPath) = find_comp(obj, path) -compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = obj.namespace[comp_name] +compdef(obj::AbstractCompositeComponentDef, comp_name::Symbol) = components(obj)[comp_name] -has_comp(c::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(c.namespace, comp_name) - -compdefs(c::AbstractCompositeComponentDef) = values(components(c)) -compkeys(c::AbstractCompositeComponentDef) = keys(components(c)) +has_comp(obj::AbstractCompositeComponentDef, comp_name::Symbol) = haskey(components(obj), comp_name) +compdefs(obj::AbstractCompositeComponentDef) = values(components(obj)) +compkeys(obj::AbstractCompositeComponentDef) = keys(components(obj)) # Allows method to be called harmlessly on leaf component defs, which simplifies recursive funcs. compdefs(c::ComponentDef) = [] @@ -121,7 +120,7 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) # Remove references to the deleted comp # TBD: make this work off namespace instead comp_path = comp_def.comp_path - exports = ccd.exports + # exports = ccd.exports # deprecated # for (key, dr) in exports @@ -173,6 +172,7 @@ end # Leaf components store ParameterDefReference or VariableDefReference instances in the namespace function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol) ref = datum_reference(comp, value.name) + ref = value isa ParameterDef ? [ref] : ref _save_to_namespace(comp, key, ref) return value end @@ -183,8 +183,8 @@ function Base.setindex!(comp::AbstractCompositeComponentDef, value::NamespaceEle end # Handle case of single ParameterDefReference by converting to 1-element array -function Base.setindex!(comp::AbstractCompositeComponentDef, ref::ParameterDefReference, key::Symbol) - setindex!(comp, [ref], key) +function Base.setindex!(comp::AbstractComponentDef, ref::ParameterDefReference, key::Symbol) + _save_to_namespace(comp, key, [ref]) end # @@ -401,7 +401,6 @@ end parameter(obj::ComponentDef, name::Symbol) = _parameter(obj, name) -# TBD: modify to use namespace function parameter(obj::AbstractCompositeComponentDef, name::Symbol) if ! haskey(obj.namespace, name) error("Item $name is not present in composite component $(obj.comp_path)") @@ -541,7 +540,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param value = convert(Array{dtype, num_dims}, value) end - ti = get_time_index_position(md, comp_name, param_name) + ti = get_time_index_position(obj, comp_name, param_name) if ti != nothing # there is a time dimension T = eltype(value) @@ -575,7 +574,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param end # connect_param! calls dirty! so we don't have to - # @info "Calling connect_param!($(printable(obj === nothing ? nothing : obj.comp_id))" + # @info "Calling connect_param!($(printable(obj === nothing ? nothing : obj.comp_id)), $comp_name, $param_name)" connect_param!(obj, comp_name, param_name, param_name) nothing end diff --git a/src/core/instances.jl b/src/core/instances.jl index c1ed80ad2..e2343252c 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -12,7 +12,7 @@ modeldef(mi::ModelInstance) = mi.md """ add_comp!(obj::AbstractCompositeComponentInstance, ci::AbstractComponentInstance) -Add the (leaf or composite) component `ci` to a composite's list of components, and add +Add the (leaf or composite) component `ci` to a composite's list of components, and add the `first` and `last` of `mi` to the ends of the composite's `firsts` and `lasts` lists. """ function add_comp!(obj::AbstractCompositeComponentInstance, ci::AbstractComponentInstance) @@ -80,7 +80,7 @@ comp_paths(obj::AbstractComponentInstanceData) = getfield(obj, :comp_paths) Return the value of parameter `name` in (leaf or composite) component `ci`. """ function get_param_value(ci::AbstractComponentInstance, name::Symbol) - try + try return getproperty(ci.parameters, name) catch err if isa(err, KeyError) @@ -157,9 +157,9 @@ function Base.getindex(mi::ModelInstance, names::NTuple{N, Symbol}) where N return obj end -Base.getindex(mi::ModelInstance, comp_path::ComponentPath) = Base.getindex(mi, comp_path.names) +Base.getindex(mi::ModelInstance, comp_path::ComponentPath) = getindex(mi, comp_path.names) -Base.getindex(mi::ModelInstance, path_str::AbstractString) = Base.getindex(mi, ComponentPath(mi.md, path_str)) +Base.getindex(mi::ModelInstance, path_str::AbstractString) = getindex(mi, ComponentPath(mi.md, path_str)) function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol) if ! has_comp(obj, comp_name) @@ -170,13 +170,13 @@ end function _get_datum(ci::AbstractComponentInstance, datum_name::Symbol) vars = variables(ci) - + if datum_name in names(vars) which = vars else pars = parameters(ci) if datum_name in names(pars) - which = pars + which = pars else error("$datum_name is not a parameter or a variable in component $(ci.comp_path).") end @@ -187,7 +187,9 @@ function _get_datum(ci::AbstractComponentInstance, datum_name::Symbol) return value isa TimestepArray ? value.data : value end -Base.getindex(mi::ModelInstance, key, datum::Symbol) = _get_datum(mi[key], datum) +function Base.getindex(mi::ModelInstance, key::AbstractString, datum::Symbol) + _get_datum(mi[key], datum) +end function Base.getindex(obj::AbstractCompositeComponentInstance, comp_name::Symbol, datum::Symbol) ci = obj[comp_name] @@ -211,7 +213,7 @@ function reset_variables(ci::AbstractComponentInstance) if (T <: AbstractArray || T <: TimestepArray) && eltype(value) <: AbstractFloat fill!(value, NaN) - elseif T <: AbstractFloat || (T <: ScalarModelParameter && T.parameters[1] <: AbstractFloat) + elseif T <: AbstractFloat || (T <: ScalarModelParameter && T.parameters[1] <: AbstractFloat) setproperty!(vars, name, NaN) elseif (T <: ScalarModelParameter) # integer or bool @@ -263,7 +265,7 @@ function run_timestep(cci::AbstractCompositeComponentInstance, clock::Clock, dim return nothing end -function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), +function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), dimkeys::Union{Nothing, Dict{Symbol, Vector{T} where T <: DimensionKeyTypes}}=nothing) if (ncomps = length(components(mi))) == 0 @@ -282,7 +284,7 @@ function Base.run(mi::ModelInstance, ntimesteps::Int=typemax(Int), # recursively initializes all components init(mi, dim_val_dict) - + clock = Clock(time_keys) while ! finished(clock) run_timestep(mi, clock, dim_val_dict) diff --git a/src/core/model.jl b/src/core/model.jl index 3871d2a6e..07f89a08b 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -366,43 +366,3 @@ function Base.run(m::Model; ntimesteps::Int=typemax(Int), rebuild::Bool=false, run(mi, ntimesteps, dim_keys) nothing end - -function _show(io::IO, obj::Model, which::Symbol) - println(io, "$(length(obj.md.comp_defs))-component Mimi.Model:") - - md = obj.md - mi = obj.mi - - # println(io, " Module: $(md.module_name)") - - # println(io, " Components:") - for comp in values(md.comp_defs) - println(io, " $(comp.name)::$(comp.comp_id.module_name).$(comp.comp_id.comp_name)") - end - - if which == :full - println(io, " Dimensions:") - for (k, v) in md.dimensions - println(io, " $k => $v") - end - - println(io, " Internal Connections:") - for conn in md.internal_param_conns - println(io, " $(conn)") - end - - println(io, " External Connections:") - for conn in md.external_param_conns - println(io, " $(conn)") - end - - println(io, " Backups: $(md.backups)") - println(io, " Number type: $(md.number_type)") - - println(io, " Built: $(mi !== nothing)") - end -end - -Base.show(io::IO, obj::Model) = _show(io, obj, :short) - -Base.show(io::IO, ::MIME"text/plain", obj::Model) = _show(io, obj, :short) diff --git a/src/core/paths.jl b/src/core/paths.jl index ad838207f..f8ce616fc 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -30,13 +30,13 @@ function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeCompon # First, fix up child's namespace objs. We later recurse down the hierarchy. ns = child.namespace - root = get_root(parent) + root = get_root(parent) for (name, ref) in ns if ref isa AbstractDatumReference T = typeof(ref) ns[name] = new_ref = T(ref.name, root, path) - @info "old ref: $ref, new: $new_ref" + #@info "old ref: $ref, new: $new_ref" end end @@ -107,10 +107,10 @@ function find_comp(obj::AbstractCompositeComponentDef, path::ComponentPath) if is_abspath(path) path = rel_path(obj.comp_path, path) # @info "abspath converted to relpath is $path" - + elseif (child = find_comp(obj, head(path))) !== nothing # @info "path is unchanged: $path" - + elseif nameof(obj) == head(path) # @info "nameof(obj) == head(path); path: $(printable(path))" diff --git a/src/core/time_arrays.jl b/src/core/time_arrays.jl index f537c8a96..ff94e098f 100644 --- a/src/core/time_arrays.jl +++ b/src/core/time_arrays.jl @@ -20,9 +20,11 @@ end # Return the index position of the time dimension in the datumdef or parameter. If there is no time dimension, return nothing get_time_index_position(dims::Union{Nothing, Array{Symbol}}) = findfirst(isequal(:time), dims) -get_time_index_position(datumdef::DatumDef) = get_time_index_position(datumdef.dimensions) -get_time_index_position(param::ArrayModelParameter) = get_time_index_position(param.dimensions) -get_time_index_position(md::ModelDef, comp_name::Symbol, datum_name::Symbol) = get_time_index_position(dimensions(compdef(md, comp_name), datum_name)) +get_time_index_position(obj::Union{AbstractDatumDef, ArrayModelParameter}) = get_time_index_position(dim_names(obj)) + +function get_time_index_position(obj::AbstractCompositeComponentDef, comp_name::Symbol, datum_name::Symbol) + get_time_index_position(dim_names(compdef(obj, comp_name), datum_name)) +end const AnyIndex = Union{Int, Vector{Int}, Tuple, Colon, OrdinalRange} @@ -33,9 +35,9 @@ function _missing_data_check(data) else return data end -end +end -# Helper macro used by connector +# Helper macro used by connector macro allow_missing(expr) let e = gensym("e") retexpr = quote @@ -43,7 +45,7 @@ macro allow_missing(expr) $expr catch $e if $e isa MissingException - missing + missing else rethrow($e) end @@ -57,7 +59,7 @@ end # b. TimestepVector # -function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} +function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} data = v.data[ts.t] _missing_data_check(data) end @@ -67,7 +69,7 @@ function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, ts::Variab _missing_data_check(data) end -function Base.getindex(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.getindex(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) data = v.data[t] _missing_data_check(data) @@ -90,15 +92,15 @@ function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, i::AnyInde return v.data[i] end -function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} +function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} setindex!(v.data, val, ts.t) end function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, ts::VariableTimestep{TIMES}) where {T, TIMES} - setindex!(v.data, val, ts.t) + setindex!(v.data, val, ts.t) end -function Base.setindex!(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.setindex!(v::TimestepVector{FixedTimestep{D_FIRST, STEP}, T}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) setindex!(v.data, val, t) end @@ -129,7 +131,7 @@ Base.lastindex(v::TimestepVector) = length(v) # c. TimestepMatrix # -function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 1}, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 1}, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} data = mat.data[ts.t, idx] _missing_data_check(data) end @@ -139,7 +141,7 @@ function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T, 1}, ts::V _missing_data_check(data) end -function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 1}, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 1}, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) data = mat.data[t, idx] _missing_data_check(data) @@ -151,7 +153,7 @@ function Base.getindex(mat::TimestepMatrix{VariableTimestep{D_TIMES}, T, 1}, ts: _missing_data_check(data) end -function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 2}, idx::AnyIndex, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 2}, idx::AnyIndex, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} data = mat.data[idx, ts.t] _missing_data_check(data) end @@ -161,7 +163,7 @@ function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T, 2}, idx:: _missing_data_check(data) end -function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 2}, idx::AnyIndex, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.getindex(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 2}, idx::AnyIndex, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) data = mat.data[idx, ts.t] _missing_data_check(data) @@ -174,7 +176,7 @@ function Base.getindex(mat::TimestepMatrix{VariableTimestep{D_TIMES}, T, 2}, idx end -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 1}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 1}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} setindex!(mat.data, val, ts.t, idx) end @@ -182,7 +184,7 @@ function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T, 1}, val, setindex!(mat.data, val, ts.t, idx) end -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 1}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 1}, val, ts::FixedTimestep{T_FIRST, STEP, LAST}, idx::AnyIndex) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) setindex!(mat.data, val, t, idx) end @@ -192,7 +194,7 @@ function Base.setindex!(mat::TimestepMatrix{VariableTimestep{D_TIMES}, T, 1}, va setindex!(mat.data, val, t, idx) end -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 2}, val, idx::AnyIndex, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 2}, val, idx::AnyIndex, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} setindex!(mat.data, val, idx, ts.t) end @@ -200,7 +202,7 @@ function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T, 2}, val, setindex!(mat.data, val, idx, ts.t) end -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 2}, val, idx::AnyIndex, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{D_FIRST, STEP}, T, 2}, val, idx::AnyIndex, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, D_FIRST, T_FIRST, STEP, LAST} t = Int(ts.t + (T_FIRST - D_FIRST) / STEP) setindex!(mat.data, val, idx, t) end @@ -279,13 +281,13 @@ end function Base.getindex(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, idxs::Union{FixedTimestep{T_FIRST, STEP, LAST}, AnyIndex}...) where {T, N, ti, D_FIRST, T_FIRST, STEP, LAST} idxs1, ts, idxs2 = split_indices(idxs, ti) - t = Int(ts.t + (FIRST - TIMES[1]) / STEP) + t = Int(ts.t + (FIRST - TIMES[1]) / STEP) return arr.data[idxs1..., t, idxs2...] end function Base.getindex(arr::TimestepArray{VariableTimestep{D_TIMES}, T, N, ti}, idxs::Union{VariableTimestep{T_TIMES}, AnyIndex}...) where {T, N, ti, D_TIMES, T_TIMES} idxs1, ts, idxs2 = split_indices(idxs, ti) - t = ts.t + findfirst(isequal(T_TIMES[1]), D_TIMES) - 1 + t = ts.t + findfirst(isequal(T_TIMES[1]), D_TIMES) - 1 return arr.data[idxs1..., t, idxs2...] end @@ -301,13 +303,13 @@ end function Base.setindex!(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, val, idxs::Union{FixedTimestep{T_FIRST, STEP, LAST}, AnyIndex}...) where {T, N, ti, D_FIRST, T_FIRST, STEP, LAST} idxs1, ts, idxs2 = split_indices(idxs, ti) - t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 + t = ts.t + findfirst(isequal(T_FIRST[1]), D_FIRST) - 1 setindex!(arr.data, val, idxs1..., t, idxs2...) end function Base.setindex!(arr::TimestepArray{VariableTimestep{D_TIMES}, T, N, ti}, val, idxs::Union{VariableTimestep{T_TIMES}, AnyIndex}...) where {T, N, ti, D_TIMES, T_TIMES} idxs1, ts, idxs2 = split_indices(idxs, ti) - t = ts.t + findfirst(isequal(T_FIRST[1]), T_TIMES) - 1 + t = ts.t + findfirst(isequal(T_FIRST[1]), T_TIMES) - 1 setindex!(arr.data, val, idxs1..., t, idxs2...) end @@ -331,21 +333,21 @@ function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, v end """ - hasvalue(arr::TimestepArray, ts::FixedTimestep) + hasvalue(arr::TimestepArray, ts::FixedTimestep) Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. """ function hasvalue(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, N, ti, FIRST, STEP, LAST} - return 1 <= ts.t <= size(arr, 1) + return 1 <= ts.t <= size(arr, 1) end """ - hasvalue(arr::TimestepArray, ts::VariableTimestep) + hasvalue(arr::TimestepArray, ts::VariableTimestep) Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts`. """ function hasvalue(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, ts::VariableTimestep{TIMES}) where {T, N, ti, TIMES} - return 1 <= ts.t <= size(arr, 1) + return 1 <= ts.t <= size(arr, 1) end function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, ts::FixedTimestep{T_FIRST, STEP, LAST}) where {T, N, ti, D_FIRST, T_FIRST, STEP, LAST} @@ -353,17 +355,17 @@ function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, ts end function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N, ti}, ts::VariableTimestep{T_FIRST}) where {T, N, ti, T_FIRST, D_FIRST} - return D_FIRST[1] <= gettime(ts) <= last_period(arr) + return D_FIRST[1] <= gettime(ts) <= last_period(arr) end """ - hasvalue(arr::TimestepArray, ts::FixedTimestep, idxs::Int...) + hasvalue(arr::TimestepArray, ts::FixedTimestep, idxs::Int...) Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within indices `idxs`. Used when Array and Timestep have different FIRST, validating all dimensions. """ -function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, - ts::FixedTimestep{T_FIRST, STEP, LAST}, +function hasvalue(arr::TimestepArray{FixedTimestep{D_FIRST, STEP}, T, N, ti}, + ts::FixedTimestep{T_FIRST, STEP, LAST}, idxs::Int...) where {T, N, ti, D_FIRST, T_FIRST, STEP, LAST} return D_FIRST <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) end @@ -374,8 +376,8 @@ end Return `true` or `false`, `true` if the TimestepArray `arr` contains the Timestep `ts` within indices `idxs`. Used when Array and Timestep different TIMES, validating all dimensions. """ -function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N, ti}, - ts::VariableTimestep{T_FIRST}, +function hasvalue(arr::TimestepArray{VariableTimestep{D_FIRST}, T, N, ti}, + ts::VariableTimestep{T_FIRST}, idxs::Int...) where {T, N, ti, D_FIRST, T_FIRST} return D_FIRST[1] <= gettime(ts) <= last_period(arr) && all([1 <= idx <= size(arr, i) for (i, idx) in enumerate(idxs)]) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 3f1b44a4f..ba612c888 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -148,7 +148,7 @@ global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference @class mutable CompositeComponentDef <: ComponentDef begin #comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Binding} - exports::ExportsDict + # exports::ExportsDict internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} @@ -170,8 +170,8 @@ global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference self.comp_path = ComponentPath(self.name) # self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() + # self.exports = ExportsDict() self.bindings = Vector{Binding}() - self.exports = ExportsDict() self.internal_param_conns = Vector{InternalParameterConnection}() self.external_param_conns = Vector{ExternalParameterConnection}() self.external_params = Dict{Symbol, ModelParameter}() @@ -208,8 +208,9 @@ external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_co external_params(obj::AbstractCompositeComponentDef) = obj.external_params -exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) -is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) +# deprecated +# exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) +# is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) diff --git a/src/core/types/instances.jl b/src/core/types/instances.jl index dce3c7d00..a4c5a3ae0 100644 --- a/src/core/types/instances.jl +++ b/src/core/types/instances.jl @@ -93,7 +93,7 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro # @info "ComponentInstance evaluating $(comp_id.module_name)" module_name = comp_id.module_name - comp_module = getfield(Main, module_name) + comp_module = module_name == :Mimi ? Mimi : getfield(Main, module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) # CompositeComponentInstances use a standard method that just loops over inner components. @@ -155,36 +155,42 @@ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, vdict = Dict([:types => [], :names => [], :values => [], :paths => []]) pdict = Dict([:types => [], :names => [], :values => [], :paths => []]) - root = get_root(comp_def) # to find comp_defs by path + # TBD: may not need this for composites after all + + # comps_dict = Dict([comp.comp_name => comp for comp in comps]) - comps_dict = Dict([comp.comp_name => comp for comp in comps]) + # for (name, item) in comp_def.namespace + # # Skip component references + # item isa AbstractComponentDef && continue - for (export_name, dr) in comp_def.exports - datum_comp = find_comp(dr) - datum_name = nameof(dr) - ci = comps_dict[nameof(datum_comp)] + # # if ! item isa VariableDefReference + # # item = - datum = (is_parameter(dr) ? ci.parameters : ci.variables) - d = (is_parameter(dr) ? pdict : vdict) + # datum_comp = find_comp(dr) + # datum_name = nameof(dr) + # ci = comps_dict[nameof(datum_comp)] - # Find the position of the desired field in the named tuple - # so we can extract it's datatype. - pos = findfirst(isequal(datum_name), names(datum)) - datatypes = types(datum) - dtype = datatypes[pos] - value = getproperty(datum, datum_name) - - push!(d[:names], export_name) - push!(d[:types], dtype) - push!(d[:values], value) - push!(d[:paths], dr.comp_path) - end + # datum = (is_parameter(dr) ? ci.parameters : ci.variables) + # d = (is_parameter(dr) ? pdict : vdict) + + # # Find the position of the desired field in the named tuple + # # so we can extract it's datatype. + # pos = findfirst(isequal(datum_name), names(datum)) + # datatypes = types(datum) + # dtype = datatypes[pos] + # value = getproperty(datum, datum_name) + + # push!(d[:names], export_name) + # push!(d[:types], dtype) + # push!(d[:values], value) + # push!(d[:paths], dr.comp_path) + # end - vars = ComponentInstanceVariables(Vector{Symbol}(vdict[:names]), Vector{DataType}(vdict[:types]), + vars = ComponentInstanceVariables(Vector{Symbol}(vdict[:names]), Vector{DataType}(vdict[:types]), Vector{Any}(vdict[:values]), Vector{ComponentPath}(vdict[:paths])) - pars = ComponentInstanceParameters(Vector{Symbol}(pdict[:names]), Vector{DataType}(pdict[:types]), - Vector{Any}(pdict[:values]), Vector{ComponentPath}(pdict[:paths])) + pars = ComponentInstanceParameters(Vector{Symbol}(pdict[:names]), Vector{DataType}(pdict[:types]), + Vector{Any}(pdict[:values]), Vector{ComponentPath}(pdict[:paths])) return vars, pars end @@ -214,8 +220,14 @@ end comp_def::AbstractCompositeComponentDef, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) - (vars, pars) = _comp_instance_vars_pars(comp_def, comps) + + # TBD: eliminate type params or just hardcode empty ones here rather than calling _comp_instance_vars_pars + vars = ComponentInstanceVariables(Symbol[], DataType[], Any[], ComponentPath[]) + pars = ComponentInstanceParameters(Symbol[], DataType[], Any[], ComponentPath[]) + # (vars, pars) = _comp_instance_vars_pars(comp_def, comps) + self = new{typeof(vars), typeof(pars)}() + CompositeComponentInstance(self, comps, comp_def, vars, pars, time_bounds, name) end end diff --git a/test/test_components.jl b/test/test_components.jl index e7856b96e..d326efca9 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -4,8 +4,8 @@ using Mimi using Test import Mimi: - compdefs, compdef, compkeys, has_comp, first_period, - last_period, compmodule, compname, numcomponents, compinstance, dim_keys, dim_values + compdefs, compdef, compkeys, has_comp, first_period, + last_period, compmodule, compname, compinstance, dim_keys, dim_values my_model = Model() @@ -18,7 +18,7 @@ my_model = Model() @defcomp testcomp1 begin var1 = Variable(index=[time]) par1 = Parameter(index=[time]) - + """ Test docstring. """ @@ -30,7 +30,7 @@ end @defcomp testcomp2 begin var1 = Variable(index=[time]) par1 = Parameter(index=[time]) - + function run_timestep(p, v, d, t) v.var1[t] = p.par1[t] end @@ -40,7 +40,7 @@ end var1 = Variable(index=[time]) par1 = Parameter(index=[time]) cbox = Variable(index=[time, 5]) # anonymous dimension - + function run_timestep(p, v, d, t) v.var1[t] = p.par1[t] end @@ -97,13 +97,13 @@ add_comp!(my_model, testcomp3, :testcomp3_v2) @defcomp testcomp1 begin var1 = Variable(index=[time]) par1 = Parameter(index=[time]) - + function run_timestep(p, v, d, t) v.var1[t] = p.par1[t] end end -# 1. Test resetting the time dimension without explicit first/last values +# 1. Test resetting the time dimension without explicit first/last values cd = testcomp1 @test cd.first === nothing # original component definition's first and last values are unset diff --git a/test/test_composite.jl b/test/test_composite.jl index d477a651c..4b5b8b1ad 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -124,9 +124,9 @@ run(m) mi = m.mi +@test mi["/top/A/Comp2", :par_2_2] == collect(1.0:16.0) +@test mi["/top/A/Comp2", :var_2_1] == collect(3.0:3:48.0) @test mi["/top/A/Comp1", :var_1_1] == collect(1.0:16.0) -@test mi[ComponentPath(md, :top, :A, :Comp2), :par_2_2] == collect(1.0:16.0) -@test mi[(:top, :A, :Comp2), :var_2_1] == collect(3.0:3:48.0) @test mi["/top/B/Comp4", :par_4_1] == collect(6.0:6:96.0) end # module diff --git a/test/test_getdataframe.jl b/test/test_getdataframe.jl index 8fbef8434..0e4bb0ab2 100644 --- a/test/test_getdataframe.jl +++ b/test/test_getdataframe.jl @@ -66,7 +66,7 @@ df = getdataframe(model1, :testcomp1=>:var1, :testcomp1=>:par1, :testcomp2=>:var dim = Mimi.dimension(model1, :time) @test df.var1 == df.par1 == years @test all(ismissing, df.var2[1 : dim[late_first]-1]) -@test all(ismissing, df.par2[1 : dim[late_first]-1]) +#@test all(ismissing, df.par2[1 : dim[late_first]-1]) @test df.var2[dim[late_first] : dim[early_last]] == df.par2[dim[late_first] : dim[early_last]] == late_first:5:early_last @test all(ismissing, df.var2[dim[years[end]] : dim[early_last]]) @test all(ismissing, df.par2[dim[years[end]] : dim[early_last]]) diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index 816a6c76d..98c14d35b 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -5,9 +5,9 @@ module TestModelStructure using Test using Mimi -import Mimi: +import Mimi: connect_param!, unconnected_params, set_dimension!, build, - numcomponents, get_connections, internal_param_conns, dim_count, dim_names, + get_connections, internal_param_conns, dim_count, dim_names, modeldef, modelinstance, compdef, getproperty, setproperty!, dimension, compdefs @defcomp A begin diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index 33714b470..eed91f696 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -5,15 +5,15 @@ module TestModelStructure_VariableTimestep using Test using Mimi -import Mimi: +import Mimi: connect_param!, unconnected_params, set_dimension!, has_comp, - numcomponents, get_connections, internal_param_conns, dim_count, + get_connections, internal_param_conns, dim_count, dim_names, compdef, getproperty, setproperty!, dimension, compdefs @defcomp A begin varA::Int = Variable(index=[time]) parA::Int = Parameter() - + function run_timestep(p, v, d, t) v.varA[t] = p.parA end @@ -48,7 +48,7 @@ m = Model() set_dimension!(m, :time, years) # first and last are now disabled -# @test_throws ErrorException add_comp!(m, A, last = 2210) +# @test_throws ErrorException add_comp!(m, A, last = 2210) # @test_throws ErrorException add_comp!(m, A, first = 2010) @test_logs( From ba78763353120e9ce1b97c4004cb8b04ce3adf59 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 9 Aug 2019 16:32:50 -0700 Subject: [PATCH 70/81] WIP: - Made ComponentInstance a superclass to both LeafComponentInstance and CompositeComponentInstance. - Backed out support for linking multiple sub-comp params to a single param in a super-comp. --- src/core/build.jl | 4 +- src/core/defcomposite.jl | 56 +++++++------- src/core/defs.jl | 11 +-- src/core/instances.jl | 4 +- src/core/types/defs.jl | 2 +- src/core/types/instances.jl | 107 ++++++++++++++------------ src/utils/graph.jl | 6 +- test/test_variables_model_instance.jl | 4 +- 8 files changed, 96 insertions(+), 98 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index 24bb80a73..c202d465d 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -193,7 +193,7 @@ function _instantiate_params(comp_def::AbstractCompositeComponentDef, par_dict:: _combine_exported_pars(comp_def, par_dict) end -# Return a built leaf or composite ComponentInstance +# Return a built leaf or composite LeafComponentInstance function _build(comp_def::ComponentDef, var_dict::Dict{ComponentPath, Any}, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, @@ -205,7 +205,7 @@ function _build(comp_def::ComponentDef, pars = _instantiate_params(comp_def, par_dict) vars = var_dict[comp_def.comp_path] - return ComponentInstance(comp_def, vars, pars, time_bounds) + return LeafComponentInstance(comp_def, vars, pars, time_bounds) end function _build(comp_def::AbstractCompositeComponentDef, diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index f57af5980..516813955 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -117,10 +117,8 @@ function import_params(comp::AbstractCompositeComponentDef) # grab the already-imported items from the namespace; create a reverse-lookup map d = Dict() - for (local_name, prefs) in param_refs(comp) - for ref in prefs - d[(ref.comp_path, ref.name)] = local_name - end + for (local_name, ref) in param_refs(comp) + d[(ref.comp_path, ref.name)] = local_name end #@info "import_params: reverse lookup: $d" @@ -129,11 +127,9 @@ function import_params(comp::AbstractCompositeComponentDef) for (comp_name, sub_comp) in components(comp) path = sub_comp.comp_path #@info " path: $path" - for (local_name, prefs) in param_refs(sub_comp) - for ref in prefs - if ! haskey(d, (ref.comp_path, ref.name)) - comp[local_name] = ref # import it - end + for (local_name, ref) in param_refs(sub_comp) + if ! haskey(d, (ref.comp_path, ref.name)) + comp[local_name] = ref # import it end end end @@ -238,33 +234,39 @@ macro defcomposite(cc_name, ex) Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) end + function _store_in_ns(refs) + isempty(refs) && return + + if length(refs) == 1 + $cc_name[local_name] = refs[1] + else + # We will eventually allow linking parameters, but not variables. For now, neither. + error("Variables and parameters must be aliased only individually.") + end + end + + # This is more complicated than needed for now since we're leaving in place some of + # the structure to accommodate linking multiple parameters to a single imported name. + # We're postponing this feature to accelerate merging the component branch and will + # return to this later. for (local_name, item) in imports - refs = [] + var_refs = [] + par_refs = [] for (src_path, src_name) in item dr = Mimi.DatumReference(src_name, $cc_name, src_path) - var_par_ref = (Mimi.is_parameter(dr) ? Mimi.ParameterDefReference(dr) : Mimi.VariableDefReference(dr)) - push!(refs, var_par_ref) - end - - # we allow linking parameters, but not variables. - count = length(refs) - if count == 1 && refs[1] isa Mimi.VariableDefReference - $cc_name[local_name] = refs[1] # store single VariableDefReference; multiples not supported - else - if count > 1 - vars = filter(obj -> obj isa Mimi.VariableDefReference, refs) - if length(vars) > 0 - error("Variables ($vars) must be aliased only individually.") - end + if Mimi.is_parameter(dr) + push!(par_refs, Mimi.ParameterDefReference(dr)) + else + push!(var_refs, Mimi.VariableDefReference(dr)) end - - $cc_name[local_name] = Vector{Mimi.ParameterDefReference}(refs) # tweak array type end + + _store_in_ns(var_refs) + _store_in_ns(par_refs) end Mimi.import_params($cc_name) - $cc_name end ) diff --git a/src/core/defs.jl b/src/core/defs.jl index 8c1a3cce6..ad093f2a6 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -10,8 +10,8 @@ istype(T::DataType) = (pair -> pair.second isa T) # N.B. only composites hold comps in the namespace. components(obj::AbstractCompositeComponentDef) = filter(istype(AbstractComponentDef), obj.namespace) -var_refs(obj::AbstractComponentDef) = filter(istype(VariableDefReference), obj.namespace) -param_refs(obj::AbstractComponentDef) = filter(istype(Vector{ParameterDefReference}), obj.namespace) +var_refs(obj::AbstractComponentDef) = filter(istype(VariableDefReference), obj.namespace) +param_refs(obj::AbstractComponentDef) = filter(istype(ParameterDefReference), obj.namespace) Base.length(obj::AbstractComponentDef) = 0 # no sub-components Base.length(obj::AbstractCompositeComponentDef) = length(components(obj)) @@ -172,21 +172,14 @@ end # Leaf components store ParameterDefReference or VariableDefReference instances in the namespace function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol) ref = datum_reference(comp, value.name) - ref = value isa ParameterDef ? [ref] : ref _save_to_namespace(comp, key, ref) return value end - function Base.setindex!(comp::AbstractCompositeComponentDef, value::NamespaceElement, key::Symbol) _save_to_namespace(comp, key, value) end -# Handle case of single ParameterDefReference by converting to 1-element array -function Base.setindex!(comp::AbstractComponentDef, ref::ParameterDefReference, key::Symbol) - _save_to_namespace(comp, key, [ref]) -end - # # Dimensions # diff --git a/src/core/instances.jl b/src/core/instances.jl index e2343252c..e31853ccc 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -75,7 +75,7 @@ end comp_paths(obj::AbstractComponentInstanceData) = getfield(obj, :comp_paths) """ - get_param_value(ci::ComponentInstance, name::Symbol) + get_param_value(ci::AbstractComponentInstance, name::Symbol) Return the value of parameter `name` in (leaf or composite) component `ci`. """ @@ -92,7 +92,7 @@ function get_param_value(ci::AbstractComponentInstance, name::Symbol) end """ - get_var_value(ci::ComponentInstance, name::Symbol) + get_var_value(ci::AbstractComponentInstance, name::Symbol) Return the value of variable `name` in component `ci`. """ diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index ba612c888..539eb7414 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -143,7 +143,7 @@ end global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} global const ExportsDict = Dict{Symbol, AbstractDatumReference} -global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference, Vector{ParameterDefReference}} +global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference, ParameterDefReference} @class mutable CompositeComponentDef <: ComponentDef begin #comps_dict::OrderedDict{Symbol, AbstractComponentDef} diff --git a/src/core/types/instances.jl b/src/core/types/instances.jl index a4c5a3ae0..5e7395fb1 100644 --- a/src/core/types/instances.jl +++ b/src/core/types/instances.jl @@ -63,36 +63,59 @@ end # as the "d" parameter. Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[property] -@class mutable ComponentInstance{TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} <: MimiClass begin +# Superclass for both LeafComponentInstance and CompositeComponentInstance. +# This allows the former to be type-parameterized and the latter to not be. +@class mutable ComponentInstance <: MimiClass begin comp_name::Symbol comp_id::ComponentId comp_path::ComponentPath - variables::TV # TBD: write functions to extract these from type instead of storing? - parameters::TP first::Union{Nothing, Int} last::Union{Nothing, Int} - init::Union{Nothing, Function} - run_timestep::Union{Nothing, Function} function ComponentInstance(self::AbstractComponentInstance, comp_def::AbstractComponentDef, - vars::TV, pars::TP, time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - + name::Symbol=nameof(comp_def)) + self.comp_name = name self.comp_id = comp_id = comp_def.comp_id self.comp_path = comp_def.comp_path - self.comp_name = name - self.variables = vars - self.parameters = pars # If first or last is `nothing`, substitute first or last time period self.first = @or(comp_def.first, time_bounds[1]) self.last = @or(comp_def.last, time_bounds[2]) + end + + function ComponentInstance(comp_def::AbstractComponentDef, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) + self = new() + return ComponentInstance(self, comp_def, time_bounds, name) + end +end + +@class mutable LeafComponentInstance{TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} <: ComponentInstance begin + variables::TV # TBD: write functions to extract these from type instead of storing? + parameters::TP + init::Union{Nothing, Function} + run_timestep::Union{Nothing, Function} - # @info "ComponentInstance evaluating $(comp_id.module_name)" - module_name = comp_id.module_name + function LeafComponentInstance(self::AbstractComponentInstance, + comp_def::AbstractComponentDef, + vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) where + {TV <: ComponentInstanceVariables, + TP <: ComponentInstanceParameters} + + # superclass initializer + ComponentInstance(self, comp_def, time_bounds, name) + + self.variables = vars + self.parameters = pars + + # @info "LeafComponentInstance evaluating $(self.comp_id.module_name)" + module_name = self.comp_id.module_name comp_module = module_name == :Mimi ? Mimi : getfield(Main, module_name) # The try/catch allows components with no run_timestep function (as in some of our test cases) @@ -112,8 +135,8 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro end end - # `is_composite` indicates a ComponentInstance used to store summary - # data for ComponentInstance and is not itself runnable. + # `is_composite` indicates a LeafComponentInstance used to store summary + # data for LeafComponentInstance and is not itself runnable. self.init = get_func("init") self.run_timestep = get_func("run_timestep") @@ -121,31 +144,26 @@ Base.getproperty(obj::DimValueDict, property::Symbol) = getfield(obj, :dict)[pro end # Create an empty instance with the given type parameters - function ComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} + function LeafComponentInstance{TV, TP}() where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} return new{TV, TP}() end end -function ComponentInstance(comp_def::AbstractComponentDef, vars::TV, pars::TP, - time_bounds::Tuple{Int,Int}, - name::Symbol=nameof(comp_def)) where +function LeafComponentInstance(comp_def::AbstractComponentDef, vars::TV, pars::TP, + time_bounds::Tuple{Int,Int}, + name::Symbol=nameof(comp_def)) where {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - self = ComponentInstance{TV, TP}() - return ComponentInstance(self, comp_def, vars, pars, time_bounds, name) + self = LeafComponentInstance{TV, TP}() + return LeafComponentInstance(self, comp_def, vars, pars, time_bounds, name) end # These can be called on CompositeComponentInstances and ModelInstances compdef(obj::AbstractComponentInstance) = compdef(comp_id(obj)) pathof(obj::AbstractComponentInstance) = obj.comp_path -has_dim(obj::AbstractComponentInstance, name::Symbol) = haskey(obj.dim_value_dict, name) -dimension(obj::AbstractComponentInstance, name::Symbol) = obj.dim_value_dict[name] first_period(obj::AbstractComponentInstance) = obj.first last_period(obj::AbstractComponentInstance) = obj.last -# -# Include only exported vars and pars -# """ Return the ComponentInstanceParameters/Variables exported by the given list of component instances. @@ -155,9 +173,7 @@ function _comp_instance_vars_pars(comp_def::AbstractCompositeComponentDef, vdict = Dict([:types => [], :names => [], :values => [], :paths => []]) pdict = Dict([:types => [], :names => [], :values => [], :paths => []]) - # TBD: may not need this for composites after all - - # comps_dict = Dict([comp.comp_name => comp for comp in comps]) + comps_dict = Dict([comp.comp_name => comp for comp in comps]) # for (name, item) in comp_def.namespace # # Skip component references @@ -196,12 +212,12 @@ end @class mutable CompositeComponentInstance <: ComponentInstance begin comps_dict::OrderedDict{Symbol, AbstractComponentInstance} + var_dict::OrderedDict{Symbol, Any} + par_dict::OrderedDict{Symbol, Any} function CompositeComponentInstance(self::AbstractCompositeComponentInstance, comps::Vector{<: AbstractComponentInstance}, comp_def::AbstractCompositeComponentDef, - vars::ComponentInstanceVariables, - pars::ComponentInstanceParameters, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) @@ -210,25 +226,20 @@ end comps_dict[ci.comp_name] = ci end - ComponentInstance(self, comp_def, vars, pars, time_bounds, name) - CompositeComponentInstance(self, comps_dict) + var_dict = OrderedDict{Symbol, Any}() + par_dict = OrderedDict{Symbol, Any}() + + ComponentInstance(self, comp_def, time_bounds, name) + CompositeComponentInstance(self, comps_dict, var_dict, par_dict) return self end - # Constructs types of vars and params from sub-components + # TBD: Construct vars and params from sub-components function CompositeComponentInstance(comps::Vector{<: AbstractComponentInstance}, comp_def::AbstractCompositeComponentDef, time_bounds::Tuple{Int,Int}, name::Symbol=nameof(comp_def)) - - # TBD: eliminate type params or just hardcode empty ones here rather than calling _comp_instance_vars_pars - vars = ComponentInstanceVariables(Symbol[], DataType[], Any[], ComponentPath[]) - pars = ComponentInstanceParameters(Symbol[], DataType[], Any[], ComponentPath[]) - # (vars, pars) = _comp_instance_vars_pars(comp_def, comps) - - self = new{typeof(vars), typeof(pars)}() - - CompositeComponentInstance(self, comps, comp_def, vars, pars, time_bounds, name) + CompositeComponentInstance(new(), comps, comp_def, time_bounds, name) end end @@ -237,17 +248,11 @@ components(obj::AbstractCompositeComponentInstance) = values(obj.comps_dict) has_comp(obj::AbstractCompositeComponentInstance, name::Symbol) = haskey(obj.comps_dict, name) compinstance(obj::AbstractCompositeComponentInstance, name::Symbol) = obj.comps_dict[name] -is_leaf(ci::AbstractComponentInstance) = true +is_leaf(ci::LeafComponentInstance) = true is_leaf(ci::AbstractCompositeComponentInstance) = false is_composite(ci::AbstractComponentInstance) = !is_leaf(ci) # ModelInstance holds the built model that is ready to be run @class ModelInstance <: CompositeComponentInstance begin md::ModelDef - - # Similar to generated constructor, but extract {TV, TP} from argument. - function ModelInstance(cci::CompositeComponentInstance{TV, TP}, md::ModelDef) where - {TV <: ComponentInstanceVariables, TP <: ComponentInstanceParameters} - return ModelInstance{TV, TP}(cci, md) - end end diff --git a/src/utils/graph.jl b/src/utils/graph.jl index 6227aa9c8..90b3904d1 100644 --- a/src/utils/graph.jl +++ b/src/utils/graph.jl @@ -48,11 +48,9 @@ function _filter_connections(conns::Vector{InternalParameterConnection}, comp_pa return collect(Iterators.filter(f, conns)) end -function get_connections(m::Model, ci::ComponentInstance, which::Symbol) - if is_leaf(ci) - return get_connections(m, pathof(ci), which) - end +get_connections(m::Model, ci::LeafComponentInstance, which::Symbol) = get_connections(m, pathof(ci), which) +function get_connections(m::Model, cci::CompositeComponentInstance, which::Symbol) conns = [] for ci in components(cci) append!(conns, get_connections(m, pathof(ci), which)) diff --git a/test/test_variables_model_instance.jl b/test/test_variables_model_instance.jl index c3110fc5b..7b281d361 100644 --- a/test/test_variables_model_instance.jl +++ b/test/test_variables_model_instance.jl @@ -6,7 +6,7 @@ using Test import Mimi: variable_names, compinstance, get_var_value, get_param_value, set_param_value, set_var_value, dim_count, compdef, - ComponentInstance, AbstractComponentInstance, ComponentDef, TimestepArray, + LeafComponentInstance, AbstractComponentInstance, ComponentDef, TimestepArray, ComponentInstanceParameters, ComponentInstanceVariables my_model = Model() @@ -40,7 +40,7 @@ cdef = compdef(md, ci.comp_path) citer = components(mi) @test typeof(md) == Mimi.ModelDef && md == mi.md -@test typeof(ci) <: ComponentInstance && ci == compinstance(mi, :testcomp1) +@test typeof(ci) <: LeafComponentInstance && ci == compinstance(mi, :testcomp1) @test typeof(cdef) <: ComponentDef && cdef.comp_id == ci.comp_id @test ci.comp_name == :testcomp1 @test typeof(citer) <: Base.ValueIterator && length(citer) == 1 && eltype(citer) <: AbstractComponentInstance From eaae4ca276e74aef142d94514f572f3260f716db Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 12 Aug 2019 14:48:09 -0700 Subject: [PATCH 71/81] WIP - Bug fixing and some cleanup. All tests working currently. --- src/core/build.jl | 53 ++++++++++++++++++++------------------- src/core/connections.jl | 23 +++++++---------- src/core/defcomposite.jl | 8 +++--- src/core/defs.jl | 23 ++++++----------- src/core/dimensions.jl | 2 +- src/core/model.jl | 2 +- src/core/show.jl | 12 ++++----- test/test_defcomposite.jl | 5 +++- test/test_timesteps.jl | 1 + 9 files changed, 60 insertions(+), 69 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index c202d465d..ceb69bab2 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -13,8 +13,8 @@ function _instance_datatype(md::ModelDef, def::AbstractDatumDef) elseif ti === nothing # there's no time dimension T = Array{dtype, num_dims} - - else + + else if isuniform(md) first, stepsize = first_and_step(md) first === nothing && @warn "_instance_datatype: first === nothing" @@ -34,23 +34,23 @@ function _instantiate_datum(md::ModelDef, def::AbstractDatumDef) dtype = _instance_datatype(md, def) dims = dim_names(def) num_dims = length(dims) - + # Scalar datum if num_dims == 0 value = dtype(0) - + # Array datum, with :time dimension - elseif dims[1] == :time + elseif dims[1] == :time if num_dims == 1 value = dtype(dim_count(md, :time)) - else + else counts = dim_counts(md, Vector{Symbol}(dims)) value = dtype <: AbstractArray ? dtype(undef, counts...) : dtype(counts...) end # Array datum, without :time dimension - else + else # TBD: Handle unnamed indices properly counts = dim_counts(md, Vector{Symbol}(dims)) value = dtype <: AbstractArray ? dtype(undef, counts...) : dtype(counts...) @@ -62,7 +62,7 @@ end """ _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) -Instantiate a component `comp_def` in the model `md` and its variables (but not its +Instantiate a component `comp_def` in the model `md` and its variables (but not its parameters). Return the resulting ComponentInstanceVariables. """ function _instantiate_component_vars(md::ModelDef, comp_def::ComponentDef) @@ -132,28 +132,28 @@ end function _instantiate_vars(comp_def::AbstractCompositeComponentDef, md::ModelDef, var_dict::Dict{ComponentPath, Any}) comp_path = comp_def.comp_path # @info "_instantiate_vars composite $comp_path" - + for cd in compdefs(comp_def) _instantiate_vars(cd, md, var_dict) - end - var_dict[comp_path] = _combine_exported_vars(comp_def, var_dict) + end + var_dict[comp_path] = _combine_exported_vars(comp_def, var_dict) end # Do nothing if called on a leaf component _collect_params(comp_def::ComponentDef, var_dict, par_dict) = nothing # Recursively collect all parameters with connections to allocated storage for variables -function _collect_params(comp_def::AbstractCompositeComponentDef, - var_dict::Dict{ComponentPath, Any}, +function _collect_params(comp_def::AbstractCompositeComponentDef, + var_dict::Dict{ComponentPath, Any}, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) # depth-first search of composites for cd in compdefs(comp_def) _collect_params(cd, var_dict, par_dict) - end + end # @info "Collecting params for $(comp_def.comp_id)" - # Iterate over connections to create parameters, referencing storage in vars + # Iterate over connections to create parameters, referencing storage in vars for ipc in internal_param_conns(comp_def) src_vars = var_dict[ipc.src_comp_path] var_value_obj = get_property_obj(src_vars, ipc.src_var_name) @@ -194,7 +194,7 @@ function _instantiate_params(comp_def::AbstractCompositeComponentDef, par_dict:: end # Return a built leaf or composite LeafComponentInstance -function _build(comp_def::ComponentDef, +function _build(comp_def::ComponentDef, var_dict::Dict{ComponentPath, Any}, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, time_bounds::Tuple{Int, Int}) @@ -208,30 +208,30 @@ function _build(comp_def::ComponentDef, return LeafComponentInstance(comp_def, vars, pars, time_bounds) end -function _build(comp_def::AbstractCompositeComponentDef, - var_dict::Dict{ComponentPath, Any}, +function _build(comp_def::AbstractCompositeComponentDef, + var_dict::Dict{ComponentPath, Any}, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}, time_bounds::Tuple{Int, Int}) # @info "_build composite $(comp_def.comp_id)" # @info " var_dict $(var_dict)" # @info " par_dict $(par_dict)" - + comps = [_build(cd, var_dict, par_dict, time_bounds) for cd in compdefs(comp_def)] return CompositeComponentInstance(comps, comp_def, time_bounds) end function _build(md::ModelDef) # @info "_build(md)" - add_connector_comps(md) - + add_connector_comps!(md) + # check if all parameters are set not_set = unconnected_params(md) - # @info "not_set: $not_set" + if ! isempty(not_set) params = join(not_set, "\n ") error("Cannot build model; the following parameters are not set:\n $params") end - + var_dict = Dict{ComponentPath, Any}() # collect all var defs and par_dict = Dict{Tuple{ComponentPath, Symbol}, Any}() # store par values as we go @@ -244,7 +244,7 @@ function _build(md::ModelDef) t = dimension(md, :time) time_bounds = (firstindex(t), lastindex(t)) - propagate_time(md, t) + propagate_time!(md, t) ci = _build(md, var_dict, par_dict, time_bounds) mi = ModelInstance(ci, md) @@ -253,7 +253,8 @@ end function build(m::Model) # Reference a copy in the ModelInstance to avoid changes underfoot - m.mi = _build(deepcopy(m.md)) + md = deepcopy(m.md) + m.mi = _build(md) m.md.dirty = false return nothing end @@ -261,7 +262,7 @@ end """ create_marginal_model(base::Model, delta::Float64=1.0) -Create a `MarginalModel` where `base` is the baseline model and `delta` is the +Create a `MarginalModel` where `base` is the baseline model and `delta` is the difference used to create the `marginal` model. Return the resulting `MarginaModel` which shares the internal `ModelDef` between the `base` and `marginal`. """ diff --git a/src/core/connections.jl b/src/core/connections.jl index f5c94c038..32e2bf114 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -25,7 +25,6 @@ Remove any parameter connections for a given parameter `param_name` in a given c `comp_def` which must be a direct subcomponent of composite `obj`. """ function disconnect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) - # @info "disconnect_param! calling compdef($(printable(obj.comp_id)), $comp_name) (comp_path: $(printable(obj.comp_path)))" comp = compdef(obj, comp_name) comp === nothing && error("Did not find $comp_name in composite $(printable(obj.comp_path))") disconnect_param!(obj, comp, param_name) @@ -140,9 +139,6 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_comp_def = compdef(obj, dst_comp_path) src_comp_def = compdef(obj, src_comp_path) - # @info "src_comp_def calling compdef($(obj.comp_id), $src_comp_path)" - # src_comp_def === nothing && @info "src_comp_def === nothing" - if backup !== nothing # If value is a NamedArray, we can check if the labels match if isa(backup, NamedArray) @@ -162,7 +158,7 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_dims = dim_names(dst_param) backup = convert(Array{Union{Missing, number_type(obj)}}, backup) # converts number type and, if it's a NamedArray, it's converted to Array - first = first_period(obj, dst_comp_def) + first = first_period(obj, dst_comp_def) T = eltype(backup) @@ -176,7 +172,8 @@ function connect_param!(obj::AbstractCompositeComponentDef, if isuniform(obj) # use the first from the comp_def not the ModelDef stepsize = step_size(obj) - values = TimestepArray{FixedTimestep{first, stepsize}, T, dim_count, ti}(backup) + last = last_period(obj, dst_comp_def) + values = TimestepArray{FixedTimestep{first, stepsize, last}, T, dim_count, ti}(backup) else times = time_labels(obj) # use the first from the comp_def @@ -214,7 +211,6 @@ Try calling: error("Units of $src_comp_path:$src_var_name do not match $dst_comp_path:$dst_par_name.") end - # @info "connect($src_comp_path:$src_var_name => $dst_comp_path:$dst_par_name)" conn = InternalParameterConnection(src_comp_path, src_var_name, dst_comp_path, dst_par_name, ignoreunits, backup_param_name, offset=offset) add_internal_param_conn!(obj, conn) @@ -305,7 +301,10 @@ Thus, only the leaf (non-composite) variant of this method actually collects unc function _collect_unconnected_params(obj::ComponentDef, connected::ParamVector, unconnected::ParamVector) comp_path = obj.comp_path params = map(x->(comp_path, x), parameter_names(obj)) - append!(unconnected, setdiff(params, connected)) + diffs = setdiff(params, connected) + if ! isempty(diffs) + append!(unconnected, diffs) + end end function _collect_unconnected_params(obj::AbstractCompositeComponentDef, connected::ParamVector, unconnected::ParamVector) @@ -426,7 +425,6 @@ Add a one dimensional time-indexed array parameter indicated by `name` and """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::TimestepVector, dims) - # @info "1. set_external_array_param!: name=$name value=$value dims=$dims, setting dims to [:time]" param = ArrayModelParameter(value, [:time]) # must be :time set_external_param!(obj, name, param) end @@ -440,7 +438,6 @@ Add a multi-dimensional time-indexed array parameter `name` with value """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::TimestepArray, dims) - # @info "2. set_external_array_param!: name=$name value=$value dims=$dims" param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims) set_external_param!(obj, name, param) end @@ -453,7 +450,6 @@ Add an array type parameter `name` with value `value` and `dims` dimensions to t """ function set_external_array_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::AbstractArray, dims) - # @info "3. set_external_array_param!: name=$name value=$value dims=$dims" numtype = Union{Missing, number_type(obj)} if !(typeof(value) <: Array{numtype} || (value isa AbstractArray && eltype(value) <: numtype)) @@ -585,7 +581,7 @@ function update_params!(obj::AbstractCompositeComponentDef, parameters::Dict; up nothing end -function add_connector_comps(obj::AbstractCompositeComponentDef) +function add_connector_comps!(obj::AbstractCompositeComponentDef) conns = internal_param_conns(obj) for comp_def in compdefs(obj) @@ -596,7 +592,7 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) internal_conns = filter(x -> x.dst_comp_path == comp_path, conns) need_conn_comps = filter(x -> x.backup !== nothing, internal_conns) - isempty(need_conn_comps) || @info "Need connectors comps: $need_conn_comps" + # isempty(need_conn_comps) || @info "Need connectors comps: $need_conn_comps" for (i, conn) in enumerate(need_conn_comps) add_backup!(obj, conn.backup) @@ -612,7 +608,6 @@ function add_connector_comps(obj::AbstractCompositeComponentDef) conn_comp_name = connector_comp_name(i) # generate a new name # Add the connector component before the user-defined component that required it - # @info "add_connector_comps: add_comp!(obj, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)" conn_comp = add_comp!(obj, conn_comp_def, conn_comp_name, before=comp_name) conn_path = conn_comp.comp_path diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 516813955..1869c324b 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -234,14 +234,14 @@ macro defcomposite(cc_name, ex) Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) end - function _store_in_ns(refs) + function _store_in_ns(refs, local_name) isempty(refs) && return if length(refs) == 1 $cc_name[local_name] = refs[1] else # We will eventually allow linking parameters, but not variables. For now, neither. - error("Variables and parameters must be aliased only individually.") + error("Variables and parameters may only be aliased individually: $refs") end end @@ -262,8 +262,8 @@ macro defcomposite(cc_name, ex) end end - _store_in_ns(var_refs) - _store_in_ns(par_refs) + _store_in_ns(var_refs, local_name) + _store_in_ns(par_refs, local_name) end Mimi.import_params($cc_name) diff --git a/src/core/defs.jl b/src/core/defs.jl index ad093f2a6..6765ab8fe 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -107,7 +107,7 @@ find_last_period(comp_def::AbstractComponentDef) = @or(last_period(comp_def), la """ delete!(obj::AbstractCompositeComponentDef, component::Symbol) -Delete a `component` by name from a model definition `m`. +Delete a `component` by name from composite `ccd`. """ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) if ! has_comp(ccd, comp_name) @@ -118,16 +118,7 @@ function Base.delete!(ccd::AbstractCompositeComponentDef, comp_name::Symbol) delete!(ccd.namespace, comp_name) # Remove references to the deleted comp - # TBD: make this work off namespace instead comp_path = comp_def.comp_path - # exports = ccd.exports - - # deprecated - # for (key, dr) in exports - # if dr.comp_path == comp_path - # delete!(exports, key) - # end - # end # TBD: find and delete external_params associated with deleted component? Currently no record of this. @@ -688,7 +679,7 @@ end function _set_comps!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symbol, AbstractComponentDef}) for key in keys(components(obj)) - delete!(obj, key) + delete!(obj.namespace, key) # delete only from namespace, keeping connections end # add comps to namespace @@ -796,18 +787,18 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract end """ - propagate_time(obj::AbstractComponentDef, t::Dimension) + propagate_time!(obj::AbstractComponentDef, t::Dimension) Propagate a time dimension down through the comp def tree. """ -function propagate_time(obj::AbstractComponentDef, t::Dimension) +function propagate_time!(obj::AbstractComponentDef, t::Dimension) set_dimension!(obj, :time, t) obj.first = firstindex(t) obj.last = lastindex(t) for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes - propagate_time(c, t) + propagate_time!(c, t) end end @@ -864,7 +855,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone error("Cannot specify both 'before' and 'after' parameters") end - propagate_time(comp_def, dimension(obj, :time)) + propagate_time!(comp_def, dimension(obj, :time)) end # Copy the original so we don't step on other uses of this comp @@ -1011,7 +1002,7 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, end filter!(epc -> !(epc in remove), external_param_conns(obj)) - # Delete the old component from composite, leaving the existing parameter connections + # Delete the old component from composite's namespace only, leaving parameter connections delete!(obj.namespace, comp_name) else # Delete the old component and all its internal and external parameter connections diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index e008e6db2..c211d0dd5 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -98,7 +98,7 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: if name == :time _set_run_period!(ccd, keys[1], keys[end]) - propagate_time(ccd, dim) + propagate_time!(ccd, dim) set_uniform!(ccd, isuniform(keys)) end diff --git a/src/core/model.jl b/src/core/model.jl index 07f89a08b..31ec8264e 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -30,7 +30,7 @@ is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) @delegate connected_params(m::Model) => md @delegate unconnected_params(m::Model) => md -@delegate add_connector_comps(m::Model) => md +@delegate add_connector_comps!(m::Model) => md """ connect_param!(m::Model, dst_comp_path::ComponentPath, dst_par_name::Symbol, src_comp_path::ComponentPath, diff --git a/src/core/show.jl b/src/core/show.jl index 3e4539bd5..692d39c47 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -148,7 +148,7 @@ function show(io::IO, obj::AbstractMimiType) print(io, "($(obj.name))") fields = deleteat!([fields...], pos) end - + _show_fields(indent(io), obj, fields) end @@ -213,15 +213,15 @@ function _show(io::IO, obj::Model, which::Symbol) mi = obj.mi println(io, " Module: $(md.comp_id.module_path)") - + println(io, " Components:") for comp in values(components(md)) println(io, " $(comp.comp_id)") end - + if which == :full println(io, " Dimensions:") - for (k, v) in md.dimensions + for (k, v) in md.dim_dict println(io, " $k => $v") end @@ -234,11 +234,11 @@ function _show(io::IO, obj::Model, which::Symbol) for conn in md.external_param_conns println(io, " $(conn)") end - + println(io, " Backups: $(md.backups)") println(io, " Number type: $(md.number_type)") end - println(io, " Built: $(mi !== nothing)") + println(io, " Built: $(mi !== nothing)") end Base.show(io::IO, obj::Model) = _show(io, obj, :full) diff --git a/test/test_defcomposite.jl b/test/test_defcomposite.jl index 62ba51c83..22dfedc63 100644 --- a/test/test_defcomposite.jl +++ b/test/test_defcomposite.jl @@ -42,7 +42,10 @@ set_dimension!(m, :time, 2005:2020) foo2 = Comp2.foo # linked imports - foo = Comp1.foo, Comp2.foo + # foo = Comp1.foo, Comp2.foo + + foo1 = Comp1.foo + foo2 = Comp2.foo # connections Comp1.par_1_1 = Comp2.var_2_1 diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index 107db0f7c..b329005f4 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -223,6 +223,7 @@ add_comp!(m3, Bar2) set_param!(m3, :Foo, :inputF, 5.) connect_param!(m3, :Bar2, :inputB, :Foo, :output, zeros(length(years))) + run(m3) @test length(m3[:Foo, :output]) == 11 From 8cd8460129d4e2fae657adfa01625d7fc229307d Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 15 Aug 2019 11:46:33 -0700 Subject: [PATCH 72/81] - Removed component def's variables and parameters slots, using a single namespace dictionary instead, and modified several methods to accommodate this change. The methods param_dict() and var_dict() are used to produce these type-specific lists. - All tests currently passing. --- src/core/build.jl | 22 ---- src/core/connections.jl | 3 +- src/core/defcomp.jl | 2 - src/core/defcomposite.jl | 5 +- src/core/defs.jl | 273 +++++++++++++++------------------------ src/core/dimensions.jl | 3 - src/core/instances.jl | 9 +- src/core/types/defs.jl | 47 +------ 8 files changed, 118 insertions(+), 246 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index ceb69bab2..fb268a613 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -81,17 +81,6 @@ function _combine_exported_vars(comp_def::AbstractCompositeComponentDef, var_dic names = Symbol[] values = Any[] - # for (name, dr) in comp_def.exports - # root = dr.root === nothing ? nothing : dr.root.comp_id - # # @info "dr.root: $(printable(root)), comp_path: $(printable(dr.comp_path))" - # if is_variable(dr) - # obj = var_dict[dr.comp_path] - # value = getproperty(obj, nameof(dr)) - # push!(names, name) - # push!(values, value) - # end - # end - types = DataType[typeof(val) for val in values] paths = repeat(Any[comp_def.comp_path], length(names)) ci_vars = ComponentInstanceVariables(names, types, values, paths) @@ -102,17 +91,6 @@ end function _combine_exported_pars(comp_def::AbstractCompositeComponentDef, par_dict::Dict{Tuple{ComponentPath, Symbol}, Any}) names = Symbol[] values = Any[] - - # @info "_combine_exported_pars: $(comp_def.exports)" - - # for (name, dr) in comp_def.exports - # if is_parameter(dr) - # value = par_dict[(dr.comp_path, dr.name)] - # push!(names, name) - # push!(values, value) - # end - # end - paths = repeat(Any[comp_def.comp_path], length(names)) types = DataType[typeof(val) for val in values] return ComponentInstanceParameters(names, types, values, paths) diff --git a/src/core/connections.jl b/src/core/connections.jl index 32e2bf114..d4ba76d7c 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -299,8 +299,7 @@ connections are made to the "original" component, not to a composite that export Thus, only the leaf (non-composite) variant of this method actually collects unconnected params. """ function _collect_unconnected_params(obj::ComponentDef, connected::ParamVector, unconnected::ParamVector) - comp_path = obj.comp_path - params = map(x->(comp_path, x), parameter_names(obj)) + params = [(obj.comp_path, x) for x in parameter_names(obj)] diffs = setdiff(params, connected) if ! isempty(diffs) append!(unconnected, diffs) diff --git a/src/core/defcomp.jl b/src/core/defcomp.jl index 6e993b711..c0229c8f6 100644 --- a/src/core/defcomp.jl +++ b/src/core/defcomp.jl @@ -72,7 +72,6 @@ end # only references to vars in components contained within. function add_variable(comp_def::ComponentDef, name, datatype, dimensions, description, unit) v = VariableDef(name, comp_def.comp_path, datatype, dimensions, description, unit) - comp_def.variables[name] = v comp_def[name] = v # adds to namespace and checks for duplicate return v end @@ -84,7 +83,6 @@ end function add_parameter(comp_def::ComponentDef, name, datatype, dimensions, description, unit, default) p = ParameterDef(name, comp_def.comp_path, datatype, dimensions, description, unit, default) - comp_def.parameters[name] = p comp_def[name] = p # adds to namespace and checks for duplicate dirty!(comp_def) return p diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 1869c324b..e068d7190 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -117,7 +117,7 @@ function import_params(comp::AbstractCompositeComponentDef) # grab the already-imported items from the namespace; create a reverse-lookup map d = Dict() - for (local_name, ref) in param_refs(comp) + for (local_name, ref) in param_dict(comp) d[(ref.comp_path, ref.name)] = local_name end @@ -127,7 +127,8 @@ function import_params(comp::AbstractCompositeComponentDef) for (comp_name, sub_comp) in components(comp) path = sub_comp.comp_path #@info " path: $path" - for (local_name, ref) in param_refs(sub_comp) + for (local_name, param) in param_dict(sub_comp) + ref = (param isa DatumReference ? ref : datum_reference(sub_comp, nameof(param))) if ! haskey(d, (ref.comp_path, ref.name)) comp[local_name] = ref # import it end diff --git a/src/core/defs.jl b/src/core/defs.jl index 6765ab8fe..fd258b3b5 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -1,18 +1,3 @@ -""" - istype(T::DataType) - -Return an anonymous func that can be used to filter a dict by data type of values. -Example: `filter(istype(AbstractComponentDef), obj.namespace)` -""" -istype(T::DataType) = (pair -> pair.second isa T) - -# Namespace filter functions; these return dicts of values for the given type. -# N.B. only composites hold comps in the namespace. -components(obj::AbstractCompositeComponentDef) = filter(istype(AbstractComponentDef), obj.namespace) - -var_refs(obj::AbstractComponentDef) = filter(istype(VariableDefReference), obj.namespace) -param_refs(obj::AbstractComponentDef) = filter(istype(ParameterDefReference), obj.namespace) - Base.length(obj::AbstractComponentDef) = 0 # no sub-components Base.length(obj::AbstractCompositeComponentDef) = length(components(obj)) @@ -131,22 +116,58 @@ end @delegate Base.haskey(comp::AbstractComponentDef, key::Symbol) => namespace -function Base.getindex(comp::AbstractComponentDef, key::Symbol) - value = comp.namespace[key] - return value +Base.getindex(comp::AbstractComponentDef, key::Symbol) = comp.namespace[key] - # value isa AbstractComponentDef && return value - - # # Variables can't be linked (not an array of values). - # # If there are linked params, all have the same value, use first. - # # If not linked, params are still stored as vector of length 1. - # ref = (value isa Vector ? value[1] : value) - - # # follow reference to access value of parameter - # obj = find_comp(ref.root, ref.comp_path) - # obj === nothing && error("Failed to find referenced parameter: $ref") +# +# Component namespaces +# +""" + istype(T::DataType) + +Return an anonymous func that can be used to filter a dict by data type of values. +Example: `filter(istype(AbstractComponentDef), obj.namespace)` +""" +istype(T::DataType) = (pair -> pair.second isa T) + +# Namespace filter functions return dicts of values for the given type. +# N.B. only composites hold comps in the namespace. +components(obj::AbstractCompositeComponentDef) = filter(istype(AbstractComponentDef), obj.namespace) + +param_dict(obj::ComponentDef) = filter(istype(ParameterDef), obj.namespace) +param_dict(obj::AbstractCompositeComponentDef) = filter(istype(ParameterDefReference), obj.namespace) + +var_dict(obj::ComponentDef) = filter(istype(VariableDef), obj.namespace) +var_dict(obj::AbstractCompositeComponentDef) = filter(istype(VariableDefReference), obj.namespace) - # return obj[ref.name] +""" + parameters(comp_def::AbstractComponentDef) + +Return an iterator of the parameter definitions (or references) for `comp_def`. +""" +parameters(obj::AbstractComponentDef) = values(param_dict(obj)) + + +""" + variables(comp_def::AbstractComponentDef) + +Return an iterator of the variable definitions (or references) for `comp_def`. +""" +variables(obj::ComponentDef) = values(filter(istype(VariableDef), obj.namespace)) +variables(obj::AbstractCompositeComponentDef) = values(filter(istype(VariableDefReference), obj.namespace)) + +variables(comp_id::ComponentId) = variables(compdef(comp_id)) +parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) + +# Return true if the component namespace has an item `name` that isa `T` +function _ns_has(comp_def::AbstractComponentDef, name::Symbol, T::DataType) + return haskey(comp_def.namespace, name) && comp_def.namespace[name] isa T +end + +function _ns_get(obj::AbstractComponentDef, name::Symbol, T::DataType) + haskey(obj.namespace, name) || error("Item :$name was not found in component $(obj.comp_path)") + item = obj[name] + item isa T || error(":$name in component $(obj.comp_path) is a $(typeof(item)); expected type $T") + return item end function _save_to_namespace(comp::AbstractComponentDef, key::Symbol, value::NamespaceElement) @@ -162,12 +183,29 @@ end # Leaf components store ParameterDefReference or VariableDefReference instances in the namespace function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol) - ref = datum_reference(comp, value.name) - _save_to_namespace(comp, key, ref) - return value + _save_to_namespace(comp, key, value) end -function Base.setindex!(comp::AbstractCompositeComponentDef, value::NamespaceElement, key::Symbol) +""" + datum_reference(comp::ComponentDef, datum_name::Symbol) + +Create a reference to the given datum, which must already exist. +""" +function datum_reference(comp::ComponentDef, datum_name::Symbol) + obj = _ns_get(comp, datum_name, AbstractDatumDef) + path = @or(obj.comp_path, ComponentPath(comp.name)) + ref_type = obj isa ParameterDef ? ParameterDefReference : VariableDefReference + return ref_type(datum_name, get_root(comp), path) +end + +""" + datum_reference(comp::AbstractCompositeComponentDef, datum_name::Symbol) + +Create a reference to the given datum, which itself must be a DatumReference. +""" +datum_reference(comp::AbstractCompositeComponentDef, datum_name::Symbol) = _ns_get(comp, datum_name, AbstractDatumReference) + +function Base.setindex!(comp::AbstractCompositeComponentDef, value::CompositeNamespaceElement, key::Symbol) _save_to_namespace(comp, key, value) end @@ -296,39 +334,6 @@ isuniform(values) = (length(values) == 0 ? false : all_equal(diff(collect(values # needed when time dimension is defined using a single integer isuniform(values::Int) = true -# -# Data references -# - -function _store_datum_ref(ccd::AbstractCompositeComponentDef, dr::ParameterDefReference, name::Symbol) - ccd.parameters[name] = parameter(dr) -end - -function _store_datum_ref(ccd::AbstractCompositeComponentDef, dr::VariableDefReference, name::Symbol) - ccd.variables[name] = variable(dr) -end - -# Define this no-op for leaf components, to simplify coding -_collect_data_refs(cd::ComponentDef; reset::Bool=false) = nothing - -function _collect_data_refs(ccd::AbstractCompositeComponentDef; reset::Bool=false) - if reset - empty!(ccd.variables) - empty!(ccd.parameters) - end - - for (name, dr) in ccd.exports - _store_datum_ref(ccd, dr, name) - end - - # recurse down composite tree - for obj in compdefs(ccd) - _collect_data_refs(obj, reset=reset) - end - - nothing -end - # # Parameters # @@ -336,31 +341,6 @@ end # Callable on both ParameterDef and VariableDef dim_names(obj::AbstractDatumDef) = obj.dim_names -""" - parameters(comp_def::ComponentDef) - -Return a list of the parameter definitions for `comp_def`. -""" -parameters(obj::AbstractComponentDef) = values(obj.parameters) - -# TBD: deprecated -function parameters(ccd::AbstractCompositeComponentDef; reset::Bool=false) - pars = ccd.parameters - - if reset || (ccd isa ModelDef && dirty(ccd)) || length(pars) == 0 - _collect_data_refs(ccd; reset=reset) - end - - return values(pars) -end - -""" - parameters(comp_id::ComponentId) - -Return a list of the parameter definitions for `comp_id`. -""" -parameters(comp_id::ComponentId) = parameters(compdef(comp_id)) - """ parameter_names(md::ModelDef, comp_name::Symbol) @@ -368,40 +348,27 @@ Return a list of all parameter names for a given component `comp_name` in a mode """ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, comp_name)) -#parameter_names(comp_def::ComponentDef) = [nameof(param) for param in parameters(comp_def)] -parameter_names(comp_def::AbstractComponentDef) = collect(keys(comp_def.parameters)) +parameter_names(comp_def::AbstractComponentDef) = collect(keys(param_dict(comp_def))) -parameter(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) +parameter(obj::ComponentDef, name::Symbol) = _ns_get(obj, name, ParameterDef) -parameter(dr::ParameterDefReference) = parameter(compdef(dr), nameof(dr)) - -function _parameter(obj::AbstractComponentDef, name::Symbol) - if haskey(obj.parameters, name) - return obj.parameters[name] - end +parameter(obj::AbstractCompositeComponentDef, name::Symbol) = _ns_get(obj, name, ParameterDefReference) - error("Parameter $name was not found in component $(nameof(obj))") -end - -parameter(obj::ComponentDef, name::Symbol) = _parameter(obj, name) +parameter(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) -function parameter(obj::AbstractCompositeComponentDef, name::Symbol) - if ! haskey(obj.namespace, name) - error("Item $name is not present in composite component $(obj.comp_path)") - end +parameter(dr::ParameterDefReference) = parameter(compdef(dr), nameof(dr)) - _parameter(obj, name) -end +has_parameter(comp_def::ComponentDef, name::Symbol) = _ns_has(comp_def, name, ParameterDef) -has_parameter(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.parameters, name) +has_parameter(comp_def::AbstractCompositeComponentDef, name::Symbol) = _ns_has(comp_def, name, ParameterDefReference) function parameter_unit(obj::AbstractComponentDef, param_name::Symbol) - param = _parameter(obj, param_name) + param = parameter(obj, param_name) return param.unit end function parameter_dimensions(obj::AbstractComponentDef, param_name::Symbol) - param = _parameter(obj, param_name) + param = parameter(obj, param_name) return dim_names(param) end @@ -566,43 +533,9 @@ end # # Variables # +variable(obj::ComponentDef, name::Symbol) = _ns_get(obj, name, VariableDef) -# Leaf components -variables(comp_def::AbstractComponentDef) = values(comp_def.variables) - -# Composite components -# TBD: if we maintain vars/pars dynamically, this can be dropped -function variables(ccd::AbstractCompositeComponentDef; reset::Bool=false) - vars = ccd.variables - - if reset || (ccd isa ModelDef && dirty(ccd)) || length(vars) == 0 - _collect_data_refs(ccd; reset=reset) - end - - return values(vars) -end - -variables(comp_id::ComponentId) = variables(compdef(comp_id)) - -function _variable(obj::AbstractComponentDef, name::Symbol) - try - return obj.variables[name] - catch - error("Variable $name was not found in component $(nameof(obj))") - end -end - -variable(obj::ComponentDef, name::Symbol) = _variable(obj, name) - -function variable(obj::AbstractCompositeComponentDef, name::Symbol) - _collect_data_refs(obj) - - if ! haskey(obj.namespace, name) - error("Item $name is not present in composite component $(obj.comp_path)") - end - - _variable(obj, name) -end +variable(obj::AbstractCompositeComponentDef, name::Symbol) = _ns_get(obj, name, VariableDefReference) variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), var_name) @@ -615,7 +548,11 @@ end variable(dr::VariableDefReference) = variable(compdef(dr), nameof(dr)) -has_variable(comp_def::AbstractComponentDef, name::Symbol) = haskey(comp_def.variables, name) + + +has_variable(comp_def::ComponentDef, name::Symbol) = _ns_has(comp_def, name, VariableDef) + +has_variable(comp_def::AbstractCompositeComponentDef, name::Symbol) = _ns_has(comp_def, name, VariableDefReference) """ variable_names(md::AbstractCompositeComponentDef, comp_name::Symbol) @@ -769,11 +706,11 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract # @info "comp path: $path, datum_name: $datum_name" # TBD: should be obviated by namespace - if is_composite(comp_def) - # find and cache locally exported vars & pars - variables(comp_def) - parameters(comp_def) - end + # if is_composite(comp_def) + # # find and cache locally exported vars & pars + # variables(comp_def) + # parameters(comp_def) + # end if has_variable(comp_def, datum_name) return VariableDefReference(datum_name, root, path) @@ -805,21 +742,16 @@ end """ add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; - exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) + first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. Note that a copy of `comp_def` is created and inserted into the composite under the given `comp_name`. -The `exports` arg identifies which vars/pars to make visible to the next higher composite level, and with -what names. If `nothing`, everything is exported. The first element of a pair indicates the symbol to export -from comp_def to the composite, the second element allows this var/par to have a new name in the composite. -A symbol alone means to use the name unchanged, i.e., [:X, :Y] implies [:X => :X, :Y => :Y] Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; - exports=nothing, # TBD: deprecated first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -878,7 +810,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone end end - # Handle special case of adding to a ModelDef, which isn't done with @defcomposite, + # Handle special case of adding to a ModelDef (which isn't done with @defcomposite) # which calls import_params after adding all components and explicit imports. obj isa AbstractModelDef && import_params(obj) @@ -888,7 +820,7 @@ end """ add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, - exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) + first=nothing, last=nothing, before=nothing, after=nothing) Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the @@ -898,12 +830,11 @@ Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; - exports=nothing, first::NothingInt=nothing, last::NothingInt=nothing, before::NothingSymbol=nothing, after::NothingSymbol=nothing) # println("Adding component $comp_id as :$comp_name") add_comp!(obj, compdef(comp_id), comp_name, - exports=exports, first=first, last=last, before=before, after=after) + first=first, last=last, before=before, after=after) end """ @@ -966,9 +897,9 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, # Check incoming parameters incoming_params = map(ipc -> ipc.dst_par_name, internal_param_conns(obj, comp_name)) - old_params = filter(pair -> pair.first in incoming_params, old_comp.parameters) - new_params = new_comp.parameters - if !_compare_datum(new_params, old_params) + old_params = filter(pair -> pair.first in incoming_params, param_dict(old_comp)) + new_params = param_dict(new_comp) + if ! _compare_datum(new_params, old_params) error("Cannot replace and reconnect; new component does not contain the necessary parameters.") end @@ -976,8 +907,8 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, _get_name(obj, name) = nameof(compdef(obj, :first)) outgoing_vars = map(ipc -> ipc.src_var_name, filter(ipc -> nameof(compdef(obj, ipc.src_comp_path)) == comp_name, internal_param_conns(obj))) - old_vars = filter(pair -> pair.first in outgoing_vars, old_comp.variables) - new_vars = new_comp.variables + old_vars = filter(pair -> pair.first in outgoing_vars, var_dict(old_comp)) + new_vars = var_dict(new_comp) if !_compare_datum(new_vars, old_vars) error("Cannot replace and reconnect; new component does not contain the necessary variables.") end @@ -990,7 +921,7 @@ function replace_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, @debug "Removing external parameter connection from component $comp_name; parameter $param_name no longer exists in component." push!(remove, epc) else - old_p = old_comp.parameters[param_name] + old_p = parameter(old_comp, param_name) new_p = new_params[param_name] if new_p.dim_names != old_p.dim_names error("Cannot replace and reconnect; parameter $param_name in new component has different dimensions.") diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index c211d0dd5..cab527b6e 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -73,9 +73,6 @@ dim_names(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [dimension dim_count_dict(obj::AbstractCompositeComponentDef) = Dict([name => length(value) for (name, value) in dim_dict(obj)]) -# deprecated? -#dim_key_dict(obj::AbstractCompositeComponentDef) = Dict([name => collect(keys(dim)) for (name, dim) in dimensions(obj)]) - dim_counts(obj::AbstractCompositeComponentDef, dims::Vector{Symbol}) = [length(dim) for dim in dim_names(obj, dims)] dim_count(obj::AbstractCompositeComponentDef, name::Symbol) = length(dimension(obj, name)) diff --git a/src/core/instances.jl b/src/core/instances.jl index e31853ccc..d663a74d7 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -98,8 +98,9 @@ Return the value of variable `name` in component `ci`. """ function get_var_value(ci::AbstractComponentInstance, name::Symbol) try - # println("Getting $name from $(ci.variables)") - return getproperty(ci.variables, name) + vars = ci.variables + # @info ("Getting $name from $vars") + return getproperty(vars, name) catch err if isa(err, KeyError) error("Component $(ci.comp_id) has no variable named $name") @@ -234,7 +235,7 @@ function init(ci::AbstractComponentInstance, dims::DimValueDict) reset_variables(ci) if ci.init != nothing - ci.init(ci.parameters, ci.variables, dims) + ci.init(parameters(ci), variables(ci), dims) end return nothing end @@ -250,7 +251,7 @@ _runnable(ci::AbstractComponentInstance, clock::Clock) = (ci.first <= gettime(cl function run_timestep(ci::AbstractComponentInstance, clock::Clock, dims::DimValueDict) if ci.run_timestep !== nothing && _runnable(ci, clock) - ci.run_timestep(ci.parameters, ci.variables, dims, clock.ts) + ci.run_timestep(parameters(ci), variables(ci), dims, clock.ts) end return nothing diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 539eb7414..1cdd186c6 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -37,8 +37,6 @@ end @class mutable ComponentDef <: NamedObj begin comp_id::Union{Nothing, ComponentId} # allow anonynous top-level (composite) ComponentDefs (must be referenced by a ModelDef) comp_path::Union{Nothing, ComponentPath} - variables::OrderedDict{Symbol, VariableDef} - parameters::OrderedDict{Symbol, ParameterDef} dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} namespace::OrderedDict{Symbol, Any} first::Union{Nothing, Int} @@ -68,8 +66,7 @@ end self.comp_id = comp_id self.comp_path = nothing # this is set in add_comp!() and ModelDef() - self.variables = OrderedDict{Symbol, VariableDef}() - self.parameters = OrderedDict{Symbol, ParameterDef}() + self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.namespace = OrderedDict{Symbol, Any}() self.first = self.last = nothing @@ -116,34 +113,14 @@ end @class VariableDefReference <: DatumReference -function datum_reference(comp::ComponentDef, datum_name::Symbol) - root = get_root(comp) - - # @info "compid: $(comp.comp_id)" - # @info "datum_reference: comp path: $(printable(comp.comp_path)) parent: $(printable(comp.parent))" - - if has_variable(comp, datum_name) - var = comp.variables[datum_name] - path = @or(var.comp_path, ComponentPath(comp.name)) - # @info " var path: $path)" - return VariableDefReference(datum_name, root, path) - end - - if has_parameter(comp, datum_name) - par = comp.parameters[datum_name] - path = @or(par.comp_path, ComponentPath(comp.name)) - # @info " par path: $path)" - return ParameterDefReference(datum_name, root, path) - end - - error("Component $(comp.comp_id) does not have a data item named :$datum_name") -end - # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} global const ExportsDict = Dict{Symbol, AbstractDatumReference} -global const NamespaceElement = Union{AbstractComponentDef, VariableDefReference, ParameterDefReference} +# Define which types can appear in the namespace dict for leaf and composite compdefs +global const LeafNamespaceElement = Union{VariableDef, ParameterDef} +global const CompositeNamespaceElement = Union{AbstractComponentDef, VariableDefReference, ParameterDefReference} +global const NamespaceElement = Union{LeafNamespaceElement, CompositeNamespaceElement} @class mutable CompositeComponentDef <: ComponentDef begin #comps_dict::OrderedDict{Symbol, AbstractComponentDef} @@ -190,30 +167,20 @@ function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Ve # @info "subcomp $c: module_name: $(printable(c.module_name)), calling module: $(nameof(calling_module))" comp_name = @or(c.module_name, nameof(calling_module)) subcomp_id = ComponentId(comp_name, c.comp_name) - # @info "subcomp_id: $subcomp_id" subcomp = compdef(subcomp_id, module_obj=(c.module_name === nothing ? calling_module : nothing)) - - # x = printable(subcomp === nothing ? nothing : subcomp_id) - # y = printable(composite === nothing ? nothing : comp_id) - # @info "CompositeComponentDef calling add_comp!($y, $x)" - add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) end return composite end +add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) + # TBD: Recursively compute the lists on demand? internal_param_conns(obj::AbstractCompositeComponentDef) = obj.internal_param_conns external_param_conns(obj::AbstractCompositeComponentDef) = obj.external_param_conns external_params(obj::AbstractCompositeComponentDef) = obj.external_params -# deprecated -# exported_names(obj::AbstractCompositeComponentDef) = keys(obj.exports) -# is_exported(obj::AbstractCompositeComponentDef, name::Symbol) = haskey(obj.exports, name) - -add_backup!(obj::AbstractCompositeComponentDef, backup) = push!(obj.backups, backup) - is_leaf(c::AbstractComponentDef) = true is_leaf(c::AbstractCompositeComponentDef) = false is_composite(c::AbstractComponentDef) = !is_leaf(c) From 04f4e99903bdc5ecbfd8a0ee32404f6680c4f314 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 15 Aug 2019 12:57:32 -0700 Subject: [PATCH 73/81] Replaced 'exports' optional arg to add_comp!() with 'rename', a list of pairs of the form name-in-subcomponent => name-in-composite. Note that this feature is not completely implemented yet and needs to be compatible with the new semantics in @defcomposite. --- src/core/defcomposite.jl | 30 ++---------------------------- src/core/defmodel.jl | 30 ------------------------------ src/core/defs.jl | 32 ++++++++++++++++++-------------- src/core/model.jl | 19 +++++++++++++++---- src/core/types/defs.jl | 8 +------- test/test_composite.jl | 25 ++++++++++++++++++------- test/test_connectorcomp.jl | 4 ++-- test/test_defcomposite.jl | 5 +---- test/test_timesteps.jl | 2 +- 9 files changed, 58 insertions(+), 97 deletions(-) diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index e068d7190..fd8b908b1 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -6,26 +6,6 @@ _arg_type(arg_tup) = arg_tup[2] _arg_slurp(arg_tup) = arg_tup[3] _arg_default(arg_tup) = arg_tup[4] -function _collect_exports(exprs) - # each item in exprs is either a single symbol, or an expression mapping - # one symbol to another, e.g., [:foo, :bar, :(:baz => :my_baz)]. We peel - # out the symbols to create a list of pairs. - exports = [] - # @info "_collect_exports: $exprs" - - for expr in exprs - if (@capture(expr, name_ => expname_) || @capture(expr, name_)) && - (name isa Symbol && (expname === nothing || expname isa Symbol)) - push!(exports, name => @or(expname, name)) - else - error("Elements of exports list must Symbols or Pair{Symbol, Symbol}, got $expr") - end - end - - # @info "returning $exports" - return exports -end - const NumericArray = Array{T, N} where {T <: Number, N} function _collect_bindings(exprs) @@ -69,7 +49,7 @@ function _subcomp(args, kwargs) error("Component name must be a Module.name expression or a symbol, got $arg1") end - valid_kws = (:exports, :bindings) # valid keyword args to the component() psuedo-function + valid_kws = (:bindings,) # valid keyword args to the component() psuedo-function kw = Dict([key => [] for key in valid_kws]) for (arg_name, arg_type, slurp, default) in kwarg_tups @@ -84,9 +64,8 @@ function _subcomp(args, kwargs) end end - exports = _collect_exports(kw[:exports]) bindings = _collect_bindings(kw[:bindings]) - return SubComponent(cmodule, cname, alias, exports, bindings) + return SubComponent(cmodule, cname, alias, bindings) end # Convert an expr like `a.b.c.d` to `[:a, :b, :c, :d]` @@ -144,13 +123,8 @@ are all variations on `component(...)`, which adds a component to the composite. calling signature for `component()` processed herein is: component(comp_name, local_name; - exports=[list of symbols or Pair{Symbol,Symbol}], bindings=[list Pair{Symbol, Symbol or Number or Array of Numbers}]) -In this macro, the vector of symbols to export is expressed without the `:`, e.g., -`exports=[var_1, var_2 => export_name, param_1])`. The names must be variable or -parameter names exported to the composite component being added by its sub-components. - Bindings are expressed as a vector of `Pair` objects, where the first element of the pair is the name (again, without the `:` prefix) representing a parameter in the component being added, and the second element is either a numeric constant, a matrix of the diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index b09881dda..009316c4e 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -3,36 +3,6 @@ # using MacroTools -""" -Target looks like this: - -# Test the calls the macro will produce -let calling_module = @__MODULE__ - global m = Model() - - ccname = :testcomp - ccid = ComponentId(calling_module, ccname) - comps = AbstractComponentDef[compdef(Comp1), compdef(Comp2), compdef(Comp3)] - - # TBD: need to implement this to create connections and default value - bindings = Binding[ - DatumReference(:par_1_1, Comp1) => 5, # bind Comp1.par_1_1 to constant value of 5 - DatumReference(:par_2_2, Comp2) => DatumReference(:var_1_1, Comp1), # connect target Comp2.par_2_1 to source Comp1.var_1_1 - DatumReference(:par_3_1, Comp3) => DatumReference(:var_2_1, Comp2)] - - exports = [ - DatumReference(:par_1_1, Comp1) => :c1p1, # i.e., export Comp1.par_1_1 as :c1p1 - DatumReference(:par_2_2, Comp2) => :c2p2, - DatumReference(:var_3_1, Comp3) => :c3v1] - - m.md = md = ModelDef() - CompositeComponentDef(md, ccid, comps, bindings, exports) - - set_dimension!(m, :time, 2005:2020) - nothing -end -""" - """ defmodel(model_name::Symbol, ex::Expr) diff --git a/src/core/defs.jl b/src/core/defs.jl index fd258b3b5..ad177e35b 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -636,9 +636,9 @@ end # Recursively ascend the component tree structure to find the root node get_root(node::AbstractComponentDef) = (node.parent === nothing ? node : get_root(node.parent)) -const NothingInt = Union{Nothing, Int} -const NothingSymbol = Union{Nothing, Symbol} -const ExportList = Vector{Union{Symbol, Pair{Symbol, Symbol}}} +const NothingInt = Union{Nothing, Int} +const NothingSymbol = Union{Nothing, Symbol} +const NothingPairList = Union{Nothing, Vector{Pair{Symbol, Symbol}}} function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef; before::NothingSymbol=nothing, after::NothingSymbol=nothing) @@ -741,19 +741,21 @@ end """ add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, - comp_name::Symbol=comp_def.comp_id.comp_name; + comp_name::Symbol=comp_def.comp_id.comp_name; rename=nothing, first=nothing, last=nothing, before=nothing, after=nothing) -Add the component indicated by `comp_def` to the composite components indicated by `obj`. The component -is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. Note that -a copy of `comp_def` is created and inserted into the composite under the given `comp_name`. +Add the component `comp_def` to the composite component indicated by `obj`. The component is +added at the end of the list unless one of the keywords `before` or `after` is specified. +Note that a copy of `comp_id` is made in the composite and assigned the give name. The optional +argument `rename` can be a list of pairs indicating `original_name => imported_name`. Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) + before::NothingSymbol=nothing, after::NothingSymbol=nothing, + rename::NothingPairList=nothing) if first !== nothing || last !== nothing @warn "add_comp!: Keyword arguments 'first' and 'last' are currently disabled." @@ -820,21 +822,23 @@ end """ add_comp!(obj::CompositeComponentDef, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name, - first=nothing, last=nothing, before=nothing, after=nothing) + first=nothing, last=nothing, before=nothing, after=nothing, rename=nothing) -Add the component indicated by `comp_id` to the composite component indicated by `obj`. The component -is added at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the -`comp_name` differs from that in the `comp_def`, a copy of `comp_def` is made and assigned the new name. +Add the component indicated by `comp_id` to the composite component indicated by `obj`. The +component is added at the end of the list unless one of the keywords `before` or `after` is +specified. Note that a copy of `comp_id` is made in the composite and assigned the give name. +The optional argument `rename` can be a list of pairs indicating `original_name => imported_name`. Note: `first` and `last` keywords are currently disabled. """ function add_comp!(obj::AbstractCompositeComponentDef, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - before::NothingSymbol=nothing, after::NothingSymbol=nothing) + before::NothingSymbol=nothing, after::NothingSymbol=nothing, + rename::NothingPairList=nothing) # println("Adding component $comp_id as :$comp_name") add_comp!(obj, compdef(comp_id), comp_name, - first=first, last=last, before=before, after=after) + first=first, last=last, before=before, after=after, rename=rename) end """ diff --git a/src/core/model.jl b/src/core/model.jl index 31ec8264e..26be46d77 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -119,12 +119,12 @@ model definition. """ add_comp!(m::Model, comp_id::ComponentId; comp_name::Symbol=comp_id.comp_name; - exports=nothing, first=nothing, last=nothing, before=nothing, after=nothing) + first=nothing, last=nothing, before=nothing, after=nothing, rename=nothing) Add the component indicated by `comp_id` to the model indicated by `m`. The component is added -at the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. If the -`comp_name` differs from that in the `comp_id`, a copy of `comp_id` is made and assigned the -new name. +at the end of the list unless one of the keywords `before` or `after` is specified. Note +that a copy of `comp_id` is made in the composite and assigned the give name. The optional +argument `rename` can be a list of pairs indicating `original_name => imported_name`. Note: `first` and `last` keywords are currently disabled. """ @@ -133,6 +133,17 @@ function add_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.com return ComponentReference(m.md, comp_name) end +""" + add_comp!(m::Model, comp_def::AbstractComponentDef; comp_name::Symbol=comp_id.comp_name; + first=nothing, last=nothing, before=nothing, after=nothing, rename=nothing) + +Add the component `comp_def` to the model indicated by `m`. The component is added at +the end of the list unless one of the keywords, `first`, `last`, `before`, `after`. Note +that a copy of `comp_id` is made in the composite and assigned the give name. The optional +argument `rename` can be a list of pairs indicating `original_name => imported_name`. + +Note: `first` and `last` keywords are currently disabled. +""" function add_comp!(m::Model, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; kwargs...) return add_comp!(m, comp_def.comp_id, comp_name; kwargs...) end diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 1cdd186c6..3e9eaae1e 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -97,7 +97,6 @@ struct SubComponent <: MimiStruct module_name::Union{Nothing, Symbol} comp_name::Symbol alias::Union{Nothing, Symbol} - exports::Vector{Union{Symbol, Pair{Symbol, Symbol}}} bindings::Vector{Pair{Symbol, Any}} end @@ -115,7 +114,6 @@ end # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} -global const ExportsDict = Dict{Symbol, AbstractDatumReference} # Define which types can appear in the namespace dict for leaf and composite compdefs global const LeafNamespaceElement = Union{VariableDef, ParameterDef} @@ -123,9 +121,7 @@ global const CompositeNamespaceElement = Union{AbstractComponentDef, VariableDef global const NamespaceElement = Union{LeafNamespaceElement, CompositeNamespaceElement} @class mutable CompositeComponentDef <: ComponentDef begin - #comps_dict::OrderedDict{Symbol, AbstractComponentDef} bindings::Vector{Binding} - # exports::ExportsDict internal_param_conns::Vector{InternalParameterConnection} external_param_conns::Vector{ExternalParameterConnection} @@ -146,8 +142,6 @@ global const NamespaceElement = Union{LeafNamespaceElement, CompositeNa ComponentDef(self, comp_id) # call superclass' initializer self.comp_path = ComponentPath(self.name) - # self.comps_dict = OrderedDict{Symbol, AbstractComponentDef}() - # self.exports = ExportsDict() self.bindings = Vector{Binding}() self.internal_param_conns = Vector{InternalParameterConnection}() self.external_param_conns = Vector{ExternalParameterConnection}() @@ -168,7 +162,7 @@ function CompositeComponentDef(comp_id::ComponentId, alias::Symbol, subcomps::Ve comp_name = @or(c.module_name, nameof(calling_module)) subcomp_id = ComponentId(comp_name, c.comp_name) subcomp = compdef(subcomp_id, module_obj=(c.module_name === nothing ? calling_module : nothing)) - add_comp!(composite, subcomp, @or(c.alias, c.comp_name), exports=c.exports) + add_comp!(composite, subcomp, @or(c.alias, c.comp_name)) end return composite end diff --git a/test/test_composite.jl b/test/test_composite.jl index 4b5b8b1ad..227e433db 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -5,7 +5,7 @@ using Mimi import Mimi: ComponentId, ComponentPath, DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, - Binding, ExportsDict, ModelDef, build, time_labels, compdef, find_comp + Binding, ModelDef, build, time_labels, compdef, find_comp @defcomp Comp1 begin @@ -55,20 +55,31 @@ m = Model() set_dimension!(m, :time, 2005:2020) @defcomposite A begin - component(Comp1; exports=[foo => foo1]) - component(Comp2, exports=[foo => foo2]) + component(Comp1) + component(Comp2) + + foo1 = Comp1.foo + foo2 = Comp2.foo end @defcomposite B begin - component(Comp3, exports=[foo => foo3]) # bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) - component(Comp4, exports=[foo => foo4]) + component(Comp3) # bindings=[foo => bar, baz => [1 2 3; 4 5 6]]) + component(Comp4) + + foo3 = Comp3.foo + foo4 = Comp4.foo end @defcomposite top begin - component(A; exports=[foo1 => fooA1, foo2 => fooA2]) + component(A) + + fooA1 = A.foo1 + fooA2 = A.foo2 # TBD: component B isn't getting added to mi - component(B; exports=[foo3, foo4]) + component(B) + foo3 = B.foo3 + foo4 = B.foo4 end # We have created the following composite structure: diff --git a/test/test_connectorcomp.jl b/test/test_connectorcomp.jl index bc5adbce5..ba16595a7 100644 --- a/test/test_connectorcomp.jl +++ b/test/test_connectorcomp.jl @@ -240,8 +240,8 @@ end model6 = Model() set_dimension!(model6, :time, years) -add_comp!(model6, foo, :Long; exports=[:var => :long_var]) -add_comp!(model6, foo, :Short; exports=[:var => :short_var]) #, first=late_start) +add_comp!(model6, foo, :Long; rename=[:var => :long_foo]) +add_comp!(model6, foo, :Short; rename=[:var => :short_foo]) #, first=late_start) connect_param!(model6, :Short => :par, :Long => :var) set_param!(model6, :Long, :par, years) diff --git a/test/test_defcomposite.jl b/test/test_defcomposite.jl index 22dfedc63..42a12c6e5 100644 --- a/test/test_defcomposite.jl +++ b/test/test_defcomposite.jl @@ -5,9 +5,6 @@ using Mimi using MacroTools import Mimi: ComponentPath, build - # ComponentId, , DatumReference, ComponentDef, AbstractComponentDef, CompositeComponentDef, - # Binding, ExportsDict, ModelDef, build, time_labels, compdef, find_comp - @defcomp Comp1 begin par_1_1 = Parameter(index=[time]) # external input @@ -61,7 +58,7 @@ set_param!(a, :Comp1, :foo, 10) set_param!(a, :Comp2, :foo, 4) # TBD: why does this overwrite the 10 above?? build(m) -#run(m) +run(m) end # module diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index b329005f4..81cc8bd25 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -115,7 +115,7 @@ set_dimension!(m, :time, years) # @test_throws ErrorException add_comp!(m, Foo; last=2100) foo = add_comp!(m, Foo) # DISABLED: first=first_foo) # offset for foo -bar = add_comp!(m, Bar; exports=[:output => :bar_output]) +bar = add_comp!(m, Bar) set_param!(m, :Foo, :inputF, 5.) set_param!(m, :Bar, :inputB, collect(1:length(years))) From 7fd5be9feba3a0a8b8a27cf9baa9c6e78128c1ef Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Wed, 21 Aug 2019 16:17:31 -0700 Subject: [PATCH 74/81] - Namespace import propagation is now working, as are connections defined in @defcomposite. - All tests are passing again. --- src/core/build.jl | 4 +++ src/core/connections.jl | 45 +++++++++++++++++----------- src/core/defcomposite.jl | 55 ++++++++++++----------------------- src/core/defs.jl | 63 ++++++++++++++++++++++++++-------------- src/core/paths.jl | 60 +++++++++++++++++++++++++++++--------- src/core/show.jl | 6 +++- src/core/types/core.jl | 2 +- src/core/types/defs.jl | 4 +-- test/test_composite.jl | 29 +++++++++++++----- 9 files changed, 170 insertions(+), 98 deletions(-) diff --git a/src/core/build.jl b/src/core/build.jl index fb268a613..b9a2a971d 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -230,6 +230,10 @@ function _build(md::ModelDef) end function build(m::Model) + # fix paths and propagate imports + fix_comp_paths!(m.md) + import_params!(m.md) + # Reference a copy in the ModelInstance to avoid changes underfoot md = deepcopy(m.md) m.mi = _build(md) diff --git a/src/core/connections.jl b/src/core/connections.jl index d4ba76d7c..60bbc7a34 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -8,13 +8,17 @@ Remove any parameter connections for a given parameter `param_name` in a given c `comp_def` which must be a direct subcomponent of composite `obj`. """ function disconnect_param!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, param_name::Symbol) + # If the path isn't set yet, we look for a comp in the eventual location + path = @or(comp_def.comp_path, ComponentPath(obj, comp_def.name)) + + # @info "disconnect_param!($(obj.comp_path), $path, :$param_name)" + if is_descendant(obj, comp_def) === nothing - error("Cannot disconnect a component ($comp_def.comp_path) that is not within the given composite ($(obj.comp_path))") + error("Cannot disconnect a component ($path) that is not within the given composite ($(obj.comp_path))") end - path = comp_def.comp_path - filter!(x -> !(x.dst_comp_path == path && x.dst_par_name == param_name), internal_param_conns(obj)) - filter!(x -> !(x.comp_path == path && x.param_name == param_name), external_param_conns(obj)) + filter!(x -> !(x.dst_comp_path == path && x.dst_par_name == param_name), obj.internal_param_conns) + filter!(x -> !(x.comp_path == path && x.param_name == param_name), obj.external_param_conns) dirty!(obj) end @@ -98,15 +102,15 @@ the external parameter `ext_param_name`. """ function connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) comp_def = compdef(obj, comp_name) - comp_path = comp_def.comp_path ext_param = external_param(obj, ext_param_name) if ext_param isa ArrayModelParameter _check_labels(obj, comp_def, param_name, ext_param) end - disconnect_param!(obj, comp_name, param_name) # calls dirty!() + disconnect_param!(obj, comp_def, param_name) # calls dirty!() + comp_path = @or(comp_def.comp_path, ComponentPath(obj.comp_path, comp_def.name)) conn = ExternalParameterConnection(comp_path, param_name, ext_param_name) add_external_param_conn!(obj, conn) @@ -139,6 +143,9 @@ function connect_param!(obj::AbstractCompositeComponentDef, dst_comp_def = compdef(obj, dst_comp_path) src_comp_def = compdef(obj, src_comp_path) + # @info "dst_comp_def: $dst_comp_def" + # @info "src_comp_def: $src_comp_def" + if backup !== nothing # If value is a NamedArray, we can check if the labels match if isa(backup, NamedArray) @@ -287,6 +294,12 @@ function _collect_connected_params(obj::AbstractCompositeComponentDef, connected append!(connected, union(ext_set_params, int_set_params)) end +""" + connected_params(md::ModelDef) + +Recursively search the component tree to find connected parameters in leaf components. +Return a vector of tuples of the form `(path::ComponentPath, param_name::Symbol)`. +""" function connected_params(md::ModelDef) connected = ParamVector() _collect_connected_params(md, connected) @@ -301,9 +314,7 @@ Thus, only the leaf (non-composite) variant of this method actually collects unc function _collect_unconnected_params(obj::ComponentDef, connected::ParamVector, unconnected::ParamVector) params = [(obj.comp_path, x) for x in parameter_names(obj)] diffs = setdiff(params, connected) - if ! isempty(diffs) - append!(unconnected, diffs) - end + append!(unconnected, diffs) end function _collect_unconnected_params(obj::AbstractCompositeComponentDef, connected::ParamVector, unconnected::ParamVector) @@ -360,6 +371,11 @@ function internal_param_conns(obj::AbstractCompositeComponentDef, comp_name::Sym return internal_param_conns(obj, ComponentPath(obj.comp_path, comp_name)) end +function add_internal_param_conn!(obj::AbstractCompositeComponentDef, conn::InternalParameterConnection) + push!(obj.internal_param_conns, conn) + dirty!(obj) +end + # Find external param conns for a given comp function external_param_conns(obj::AbstractCompositeComponentDef, comp_path::ComponentPath) return filter(x -> x.comp_path == comp_path, external_param_conns(obj)) @@ -377,20 +393,15 @@ function external_param(obj::AbstractCompositeComponentDef, name::Symbol; missin error("$name not found in external parameter list") end -function add_internal_param_conn!(obj::AbstractCompositeComponentDef, conn::InternalParameterConnection) - push!(obj.internal_param_conns, conn) - dirty!(obj) -end - function add_external_param_conn!(obj::AbstractCompositeComponentDef, conn::ExternalParameterConnection) push!(obj.external_param_conns, conn) dirty!(obj) end function set_external_param!(obj::AbstractCompositeComponentDef, name::Symbol, value::ModelParameter) - if haskey(obj.external_params, name) - @warn "Redefining external param :$name in $(obj.comp_path) from $(obj.external_params[name]) to $value" - end + # if haskey(obj.external_params, name) + # @warn "Redefining external param :$name in $(obj.comp_path) from $(obj.external_params[name]) to $value" + # end obj.external_params[name] = value dirty!(obj) end diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index fd8b908b1..6edc753e5 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -82,7 +82,8 @@ function _parse_dotted_symbols(expr) if ex isa Symbol push!(syms, ex) else - error("Expected Symbol or Symbol.Symbol..., got $expr") + # @warn "Expected Symbol or Symbol.Symbol..., got $expr" + return nothing end syms = reverse(syms) @@ -90,31 +91,6 @@ function _parse_dotted_symbols(expr) return ComponentPath(syms), var_or_par end -function import_params(comp::AbstractCompositeComponentDef) - # nothing to do if there are no sub-components - length(comp) == 0 && return - - # grab the already-imported items from the namespace; create a reverse-lookup map - d = Dict() - for (local_name, ref) in param_dict(comp) - d[(ref.comp_path, ref.name)] = local_name - end - - #@info "import_params: reverse lookup: $d" - - # Iterate over all sub-components and import all params not already referenced (usually renamed) - for (comp_name, sub_comp) in components(comp) - path = sub_comp.comp_path - #@info " path: $path" - for (local_name, param) in param_dict(sub_comp) - ref = (param isa DatumReference ? ref : datum_reference(sub_comp, nameof(param))) - if ! haskey(d, (ref.comp_path, ref.name)) - comp[local_name] = ref # import it - end - end - end -end - """ defcomposite(cc_name::Symbol, ex::Expr) @@ -171,15 +147,18 @@ macro defcomposite(cc_name, ex) # Save a singletons as a 1-element Vector for consistency with multiple linked params var_par = right.head == :tuple ? _parse_dotted_symbols.(right.args) : [_parse_dotted_symbols(right)] push!(imports, (left, var_par)) - @info "import as $left = $var_par" + # @info "import as $left = $var_par" # note that `comp_Symbol.name_Symbol` failed; bug in MacroTools? - elseif @capture(left, comp_.name_) && comp isa Symbol && name isa Symbol # simple connection case - src = _parse_dotted_symbols(right) + elseif @capture(left, comp_.name_) # simple connection case dst = _parse_dotted_symbols(left) - tup = (dst, src) - push!(conns, tup) - @info "connection: $dst = $src" + dst === nothing && error("Expected dot-delimited sequence of symbols, got $left") + + src = _parse_dotted_symbols(right) + src === nothing && error("Expected dot-delimited sequence of symbols, got $right") + + push!(conns, (dst, src)) + # @info "connection: $dst = $src" else error("Unrecognized expression on left hand side of '=' in @defcomposite: $elt") @@ -205,9 +184,7 @@ macro defcomposite(cc_name, ex) global $cc_name = Mimi.CompositeComponentDef(cc_id, $(QuoteNode(cc_name)), $comps, $__module__) - for ((dst_path, dst_name), (src_path, src_name)) in conns - Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) - end + # @info "Defining composite $cc_id" function _store_in_ns(refs, local_name) isempty(refs) && return @@ -241,7 +218,13 @@ macro defcomposite(cc_name, ex) _store_in_ns(par_refs, local_name) end - Mimi.import_params($cc_name) + # Mimi.import_params!($cc_name) + + for ((dst_path, dst_name), (src_path, src_name)) in conns + # @info "connect_param!($(nameof($cc_name)), $dst_path, :$dst_name, $src_path, :$src_name)" + Mimi.connect_param!($cc_name, dst_path, dst_name, src_path, src_name) + end + $cc_name end ) diff --git a/src/core/defs.jl b/src/core/defs.jl index ad177e35b..cc7841120 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -181,11 +181,6 @@ function _save_to_namespace(comp::AbstractComponentDef, key::Symbol, value::Name comp.namespace[key] = value end -# Leaf components store ParameterDefReference or VariableDefReference instances in the namespace -function Base.setindex!(comp::ComponentDef, value::AbstractDatumDef, key::Symbol) - _save_to_namespace(comp, key, value) -end - """ datum_reference(comp::ComponentDef, datum_name::Symbol) @@ -209,6 +204,11 @@ function Base.setindex!(comp::AbstractCompositeComponentDef, value::CompositeNam _save_to_namespace(comp, key, value) end +# Leaf components store ParameterDefReference or VariableDefReference instances in the namespace +function Base.setindex!(comp::ComponentDef, value::LeafNamespaceElement, key::Symbol) + _save_to_namespace(comp, key, value) +end + # # Dimensions # @@ -627,9 +627,10 @@ function _set_comps!(obj::AbstractCompositeComponentDef, comps::OrderedDict{Symb dirty!(obj) end -# Save a back-pointer to the container object +# Save a back-pointer to the container object and set the comp_path function parent!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) child.parent = parent + child.comp_path = ComponentPath(parent, child.name) nothing end @@ -678,7 +679,6 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom _set_comps!(obj, new_comps) end - comp_path!(comp_def, obj) # @info "parent obj comp_path: $(printable(obj.comp_path))" # @info "inserted comp's path: $(comp_def.comp_path)" dirty!(obj) @@ -703,15 +703,6 @@ function _find_var_par(parent::AbstractCompositeComponentDef, comp_def::Abstract root === nothing && error("Component $(parent.comp_id) does not have a root") - # @info "comp path: $path, datum_name: $datum_name" - - # TBD: should be obviated by namespace - # if is_composite(comp_def) - # # find and cache locally exported vars & pars - # variables(comp_def) - # parameters(comp_def) - # end - if has_variable(comp_def, datum_name) return VariableDefReference(datum_name, root, path) end @@ -739,6 +730,41 @@ function propagate_time!(obj::AbstractComponentDef, t::Dimension) end end +""" + import_params!(comp::AbstractComponentDef) + +Recursively (depth-first) import parameters from leaf comps to composites, and from +sub-composites to their parents. N.B. this is done in _build() after calling +fix_comp_paths!(). +""" +function import_params!(comp::AbstractComponentDef) + # nothing to do if there are no sub-components + length(comp) == 0 && return + + sub_comps = values(components(comp)) + + for sub_comp in sub_comps + import_params!(sub_comp) + end + + # grab any items imported in @defcomposite; create a reverse-lookup map + d = Dict() + for (local_name, ref) in param_dict(comp) + d[(ref.comp_path, ref.name)] = local_name + end + + # import any unreferenced (and usually renamed locally) parameters + for sub_comp in sub_comps + # N.B. param_dict() returns dict of either params (for leafs) or param refs (from composite) + for (local_name, param) in param_dict(sub_comp) + ref = (param isa AbstractDatumReference ? param : datum_reference(sub_comp, nameof(param))) + if ! haskey(d, (ref.comp_path, ref.name)) + comp[local_name] = ref # add the reference to the local namespace + end + end + end +end + """ add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; rename=nothing, @@ -806,16 +832,11 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCompone for param in parameters(comp_def) if param.default !== nothing x = printable(obj === nothing ? "obj==nothing" : obj.comp_id) - # @info "add_comp! calling set_param!($x, $comp_name, $(nameof(param)), $(param.default))" set_param!(obj, comp_name, nameof(param), param.default) end end end - # Handle special case of adding to a ModelDef (which isn't done with @defcomposite) - # which calls import_params after adding all components and explicit imports. - obj isa AbstractModelDef && import_params(obj) - # Return the comp since it's a copy of what was passed in return comp_def end diff --git a/src/core/paths.jl b/src/core/paths.jl index f8ce616fc..2bcb36fb1 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -17,17 +17,22 @@ function Base.string(path::ComponentPath) return is_abspath(path) ? string("/", s) : s end +Base.joinpath(p1::ComponentPath, p2::ComponentPath) = is_abspath(p2) ? p2 : ComponentPath(p1.names..., p2.names...) +Base.joinpath(p1::ComponentPath, other...) = joinpath(joinpath(p1, other[1]), other[2:end]...) + """ - comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) + _fix_comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) Set the ComponentPath of a child object to extend the path of its composite parent. For composites, also update the component paths for all internal connections, and -for all DatumReferences in the namespace. -For leaf components, update the ComponentPath for ParameterDefs and VariableDefs. +for all DatumReferences in the namespace. For leaf components, also update the +ComponentPath for ParameterDefs and VariableDefs. """ -function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) - child.comp_path = path = ComponentPath(parent.comp_path, child.name) - +function _fix_comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) + parent_path = parent.comp_path + child.comp_path = child_path = ComponentPath(parent_path, child.name) + # @info "Setting path of child $(child.name) with parent $parent_path to $child_path" + # First, fix up child's namespace objs. We later recurse down the hierarchy. ns = child.namespace root = get_root(parent) @@ -35,34 +40,61 @@ function comp_path!(child::AbstractComponentDef, parent::AbstractCompositeCompon for (name, ref) in ns if ref isa AbstractDatumReference T = typeof(ref) - ns[name] = new_ref = T(ref.name, root, path) + ns[name] = new_ref = T(ref.name, root, child_path) #@info "old ref: $ref, new: $new_ref" end end - # recursively reset all comp_paths + # recursively reset all comp_paths to their abspath equivalent if is_composite(child) + # Fix internal param conns conns = child.internal_param_conns for (i, conn) in enumerate(conns) - src_path = ComponentPath(path, conn.src_comp_path) - dst_path = ComponentPath(path, conn.dst_comp_path) + src_path = ComponentPath(child_path, conn.src_comp_path) + dst_path = ComponentPath(child_path, conn.dst_comp_path) + + # @info "Resetting IPC src in $child_path from $(conn.src_comp_path) to $src_path" + # @info "Resetting IPC dst in $child_path from $(conn.dst_comp_path) to $dst_path" # InternalParameterConnections are immutable, but the vector holding them is not conns[i] = InternalParameterConnection(src_path, conn.src_var_name, dst_path, conn.dst_par_name, conn.ignoreunits, conn.backup; offset=conn.offset) end + # Fix external param conns + conns = child.external_param_conns + for (i, conn) in enumerate(conns) + path = ComponentPath(parent_path, conn.comp_path) + # @info "Resetting EPC $child_path from $(conn.comp_path) to $path" + + conns[i] = ExternalParameterConnection(path, conn.param_name, conn.external_param) + end + for cd in compdefs(child) - comp_path!(cd, child) + _fix_comp_path!(cd, child) end else for datum in [variables(child)..., parameters(child)...] - datum.comp_path = path + datum.comp_path = child_path end end end +""" + fix_comp_paths!(md::AbstractModelDef) + +Recursively set the ComponentPaths in a tree below a ModelDef to the absolute path equivalent. +This includes updating the component paths for all internal/external connections, and all +DatumReferences in the namespace. For leaf components, we also update the ComponentPath for +ParameterDefs and VariableDefs. +""" +function fix_comp_paths!(md::AbstractModelDef) + for child in compdefs(md) + _fix_comp_path!(child, md) + end +end + """ comp_path(node::AbstractCompositeComponentDef, path::AbstractString) @@ -152,6 +184,8 @@ function rel_path(ancestor_path::ComponentPath, descendant_path::ComponentPath) return ComponentPath(d_names[a_len+1:end]) end +rel_path(obj::AbstractComponentDef, descendant_path::ComponentPath) = rel_path(obj.comp_path, descendant_path) + """ Return whether component `descendant` is within the composite structure of `ancestor` or any of its descendants. If the comp_paths check out, the node is located within the @@ -160,7 +194,7 @@ structure to ensure that the component is really where it says it is. (Trust but function is_descendant(ancestor::AbstractCompositeComponentDef, descendant::AbstractComponentDef) a_path = ancestor.comp_path d_path = descendant.comp_path - if (relpath = rel_path(a_path, d_path)) === nothing + if d_path === nothing || (relpath = rel_path(a_path, d_path)) === nothing return false end diff --git a/src/core/show.jl b/src/core/show.jl index 692d39c47..f2a66d600 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -152,7 +152,7 @@ function show(io::IO, obj::AbstractMimiType) _show_fields(indent(io), obj, fields) end -function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) +function show(io::IO, obj::AbstractComponentDef) print(io, nameof(typeof(obj)), " id:", objectid(obj)) fields = fieldnames(typeof(obj)) @@ -188,6 +188,10 @@ function show(io::IO, obj::Union{AbstractComponentDef, AbstractDatumReference}) end end +function show(io::IO, obj::AbstractDatumReference) + print(io, nameof(typeof(obj)), "(name=:$(obj.name) path=$(obj.comp_path))") +end + function show(io::IO, obj::ModelInstance) # Don't print full type signature since it's shown in .variables and .parameters print(io, "ModelInstance") diff --git a/src/core/types/core.jl b/src/core/types/core.jl index b5ef36019..900acf4a7 100644 --- a/src/core/types/core.jl +++ b/src/core/types/core.jl @@ -44,7 +44,7 @@ struct ComponentPath <: MimiStruct end ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) -ComponentPath(names::Vararg{Symbol}) = ComponentPath(names) +ComponentPath(names::Vararg{Symbol}) = ComponentPath(Tuple(names)) ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath(path.names..., name) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 3e9eaae1e..0d0ff1a3b 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -116,8 +116,8 @@ end global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} # Define which types can appear in the namespace dict for leaf and composite compdefs -global const LeafNamespaceElement = Union{VariableDef, ParameterDef} -global const CompositeNamespaceElement = Union{AbstractComponentDef, VariableDefReference, ParameterDefReference} +global const LeafNamespaceElement = AbstractDatumDef +global const CompositeNamespaceElement = Union{AbstractComponentDef, AbstractDatumReference} global const NamespaceElement = Union{LeafNamespaceElement, CompositeNamespaceElement} @class mutable CompositeComponentDef <: ComponentDef begin diff --git a/test/test_composite.jl b/test/test_composite.jl index 227e433db..575564702 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -60,6 +60,12 @@ set_dimension!(m, :time, 2005:2020) foo1 = Comp1.foo foo2 = Comp2.foo + + # Should accomplish the same as calling + # connect_param!(m, "/top/A/Comp2:par_2_1", "/top/A/Comp1:var_1_1") + # after the `@defcomposite top ...` + Comp2.par_2_1 = Comp1.var_1_1 + Comp2.par_2_2 = Comp1.var_1_1 end @defcomposite B begin @@ -97,9 +103,15 @@ md = m.md @test find_comp(md, :top) == top_comp +# +# Test various ways to access sub-components +# c1 = find_comp(md, ComponentPath(:top, :A, :Comp1)) @test c1.comp_id == Comp1.comp_id +c2 = md[:top][:A][:Comp2] +@test c2.comp_id == Comp2.comp_id + c3 = find_comp(md, "/top/B/Comp3") @test c3.comp_id == Comp3.comp_id @@ -114,12 +126,13 @@ set_param!(m, "/top/B/Comp4:foo", 20) set_param!(m, "/top/A/Comp1", :par_1_1, collect(1:length(time_labels(md)))) -connect_param!(m, "/top/A/Comp2:par_2_1", "/top/A/Comp1:var_1_1") -connect_param!(m, "/top/A/Comp2:par_2_2", "/top/A/Comp1:var_1_1") +# connect_param!(m, "/top/A/Comp2:par_2_1", "/top/A/Comp1:var_1_1") +# connect_param!(m, "/top/A/Comp2:par_2_2", "/top/A/Comp1:var_1_1") connect_param!(m, "/top/B/Comp3:par_3_1", "/top/A/Comp2:var_2_1") connect_param!(m, "/top/B/Comp4:par_4_1", "/top/B/Comp3:var_3_1") build(m) + run(m) # @@ -135,17 +148,19 @@ run(m) mi = m.mi +@test mi[:top][:A][:Comp2, :par_2_2] == collect(1.0:16.0) @test mi["/top/A/Comp2", :par_2_2] == collect(1.0:16.0) + @test mi["/top/A/Comp2", :var_2_1] == collect(3.0:3:48.0) @test mi["/top/A/Comp1", :var_1_1] == collect(1.0:16.0) @test mi["/top/B/Comp4", :par_4_1] == collect(6.0:6:96.0) end # module -# m = TestComposite.m -# md = m.md -# top = Mimi.find_comp(md, :top) -# A = Mimi.find_comp(top, :A) -# comp1 = Mimi.find_comp(A, :Comp1) +m = TestComposite.m +md = m.md +top = Mimi.find_comp(md, :top) +A = Mimi.find_comp(top, :A) +comp1 = Mimi.find_comp(A, :Comp1) nothing From ade53b05ae5436a6a0b9a941b867305ed8e33fb8 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Thu, 22 Aug 2019 12:50:56 -0700 Subject: [PATCH 75/81] WIP -- Continue testing composites --- src/core/defcomposite.jl | 10 +++++----- src/core/defmodel.jl | 9 +++++++-- src/core/defs.jl | 4 ++-- src/core/model.jl | 2 ++ src/core/types/defs.jl | 10 ++++++++++ src/core/types/params.jl | 1 - test/test_defcomposite.jl | 22 ++++++++++++++++------ 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 6edc753e5..2fb469b77 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -69,7 +69,7 @@ function _subcomp(args, kwargs) end # Convert an expr like `a.b.c.d` to `[:a, :b, :c, :d]` -function _parse_dotted_symbols(expr) +function parse_dotted_symbols(expr) global Args = expr syms = Symbol[] @@ -137,7 +137,7 @@ macro defcomposite(cc_name, ex) # # Here we parse everything on the right side, at once using broadcasting and add the initial # # component (immediately after "=") to the list, and then store a Vector of param refs. # args = [right, elt.args[2:end]...] - # vars_pars = _parse_dotted_symbols.(args) + # vars_pars = parse_dotted_symbols.(args) # @info "import as $left = $vars_pars" # push!(imports, (left, vars_pars)) @@ -145,16 +145,16 @@ macro defcomposite(cc_name, ex) if left isa Symbol # simple import case # Save a singletons as a 1-element Vector for consistency with multiple linked params - var_par = right.head == :tuple ? _parse_dotted_symbols.(right.args) : [_parse_dotted_symbols(right)] + var_par = right.head == :tuple ? parse_dotted_symbols.(right.args) : [parse_dotted_symbols(right)] push!(imports, (left, var_par)) # @info "import as $left = $var_par" # note that `comp_Symbol.name_Symbol` failed; bug in MacroTools? elseif @capture(left, comp_.name_) # simple connection case - dst = _parse_dotted_symbols(left) + dst = parse_dotted_symbols(left) dst === nothing && error("Expected dot-delimited sequence of symbols, got $left") - src = _parse_dotted_symbols(right) + src = parse_dotted_symbols(right) src === nothing && error("Expected dot-delimited sequence of symbols, got $right") push!(conns, (dst, src)) diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index 009316c4e..252df9ebc 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -59,8 +59,13 @@ macro defmodel(model_name, ex) elseif @capture(elt, index[idx_name_] = rhs_) expr = :(Mimi.set_dimension!($model_name, $(QuoteNode(idx_name)), $rhs)) - elseif @capture(elt, comp_name_.param_name_ = rhs_) - expr = :(Mimi.set_param!($model_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) + # elseif @capture(elt, comp_name_.param_name_ = rhs_) + # expr = :(Mimi.set_param!($model_name, $(QuoteNode(comp_name)), $(QuoteNode(param_name)), $rhs)) + + elseif @capture(elt, lhs_ = rhs_) && @capture(lhs, comp_.name_) + (path, param_name) = parse_dotted_symbols(lhs) + expr = :(Mimi.set_param!($model_name, $path, $(QuoteNode(param_name)), $rhs)) + # @info "expr: $expr" else # Pass through anything else to allow the user to define intermediate vars, etc. diff --git a/src/core/defs.jl b/src/core/defs.jl index cc7841120..13b3819fc 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -483,7 +483,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param if dtype <: AbstractArray value = convert(dtype, value) else - #check that number of dimensions matches + # check that number of dimensions matches value_dims = length(size(value)) if num_dims != value_dims error("Mismatched data size for a set parameter call: dimension :$param_name in $(comp_name) has $num_dims dimensions; indicated value has $value_dims dimensions.") @@ -508,7 +508,7 @@ function set_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param values = TimestepArray{FixedTimestep{first, stepsize}, T, num_dims, ti}(value) else times = time_labels(obj) - #use the first from the comp_def + # use the first from the comp_def first_index = findfirst(isequal(first), times) values = TimestepArray{VariableTimestep{(times[first_index:end]...,)}, T, num_dims, ti}(value) end diff --git a/src/core/model.jl b/src/core/model.jl index 26be46d77..bee29bdb3 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -356,6 +356,8 @@ Set the value of a parameter exposed in the ModelDef (m.md). """ @delegate set_param!(m::Model, param_name::Symbol, value, dims=nothing) => md +@delegate set_param!(m::Model, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) => md + """ run(m::Model) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 0d0ff1a3b..d9d46f2db 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -112,6 +112,16 @@ end @class VariableDefReference <: DatumReference +function _dereference(ref::AbstractDatumReference) + comp = find_comp(ref) + return comp[ref.name] +end + +# Might not be useful +# convert(::Type{VariableDef}, ref::VariableDefReference) = _dereference(ref) +# convert(::Type{ParameterDef}, ref::ParameterDefReference) = _dereference(ref) + + # Define type aliases to avoid repeating these in several places global const Binding = Pair{AbstractDatumReference, Union{Int, Float64, AbstractDatumReference}} diff --git a/src/core/types/params.jl b/src/core/types/params.jl index fd39ffef3..d50b6c471 100644 --- a/src/core/types/params.jl +++ b/src/core/types/params.jl @@ -46,7 +46,6 @@ value(param::ScalarModelParameter) = param.value dim_names(obj::ArrayModelParameter) = obj.dim_names dim_names(obj::ScalarModelParameter) = [] - abstract type AbstractConnection <: MimiStruct end struct InternalParameterConnection <: AbstractConnection diff --git a/test/test_defcomposite.jl b/test/test_defcomposite.jl index 42a12c6e5..7d9ec9ab0 100644 --- a/test/test_defcomposite.jl +++ b/test/test_defcomposite.jl @@ -4,7 +4,7 @@ using Test using Mimi using MacroTools -import Mimi: ComponentPath, build +import Mimi: ComponentPath, build, @defmodel @defcomp Comp1 begin par_1_1 = Parameter(index=[time]) # external input @@ -27,9 +27,6 @@ end end end -m = Model() -set_dimension!(m, :time, 2005:2020) - @defcomposite A begin component(Comp1) component(Comp2) @@ -45,15 +42,28 @@ set_dimension!(m, :time, 2005:2020) foo2 = Comp2.foo # connections - Comp1.par_1_1 = Comp2.var_2_1 Comp2.par_2_1 = Comp1.var_1_1 Comp2.par_2_2 = Comp1.var_1_1 end + +# doesn't work currently +# @defmodel m begin +# index[time] = 2005:2020 +# component(A) + +# A.foo1 = 10 +# A.foo2 = 4 +# end + +m = Model() +years = 2005:2020 +set_dimension!(m, :time, years) add_comp!(m, A) -a = m.md[:A] +set_param!(m, "/A/Comp1", :par_1_1, 2:2:2*length(years)) +a = m.md[:A] set_param!(a, :Comp1, :foo, 10) set_param!(a, :Comp2, :foo, 4) # TBD: why does this overwrite the 10 above?? From 081ab795faeeec5ed24c55bfe6e1d7a79ba1f870 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sat, 28 Sep 2019 14:53:32 -0700 Subject: [PATCH 76/81] WIP: fix tests in merger of master into class-based --- src/core/types/params.jl | 3 +++ test/test_metainfo.jl | 2 +- test/test_metainfo_variabletimestep.jl | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/types/params.jl b/src/core/types/params.jl index d50b6c471..6d361b289 100644 --- a/src/core/types/params.jl +++ b/src/core/types/params.jl @@ -43,6 +43,9 @@ ArrayModelParameter(value, dims::Vector{Symbol}) = ArrayModelParameter{typeof(va value(param::ArrayModelParameter) = param.values value(param::ScalarModelParameter) = param.value +Base.copy(obj::ScalarModelParameter{T}) where T = ScalarModelParameter(obj.value) +Base.copy(obj::ArrayModelParameter{T}) where T = ArrayModelParameter(obj.values, obj.dim_names) + dim_names(obj::ArrayModelParameter) = obj.dim_names dim_names(obj::ScalarModelParameter) = [] diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 59ab475a9..56521028f 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -36,7 +36,7 @@ end end c0 = ch4forcing1 -@test compmodule(c0) == :TestMetaInfo +@test compmodule(c0) == TestMetaInfo @test compname(c0) == :ch4forcing1 @test nameof(c0) == :ch4forcing1 diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index 1ccf863dc..8fe34fe42 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -38,7 +38,7 @@ end c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) -@test compmodule(c2) == :TestMetaInfo_VariableTimestep +@test compmodule(c2) == TestMetaInfo_VariableTimestep # TBD: old tests; might still work @test c1.comp_id == ch4forcing1.comp_id From 48724ae0176e16507f11ed458499373823525535 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sat, 28 Sep 2019 15:35:30 -0700 Subject: [PATCH 77/81] WIP: All Mimi tests are passing on the merged 'class-based' branch. Next: test all models. --- src/core/model.jl | 2 +- src/mcs/mcs_types.jl | 4 ++-- src/mcs/montecarlo.jl | 20 ++++++++++---------- src/utils/getdataframe.jl | 12 ++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/core/model.jl b/src/core/model.jl index ceaf7b8e3..c54c032f5 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -16,7 +16,7 @@ modelinstance_def(m::Model) = modeldef(modelinstance(m)) is_built(m::Model) = !(dirty(m.md) || modelinstance(m) === nothing) -is_built(mm::MarginalModel) = (is_built(mm.base) && is_build(mm.marginal)) +is_built(mm::MarginalModel) = (is_built(mm.base) && is_built(mm.marginal)) @delegate compinstance(m::Model, name::Symbol) => mi @delegate has_comp(m::Model, name::Symbol) => md diff --git a/src/mcs/mcs_types.jl b/src/mcs/mcs_types.jl index 7a6033b7e..ec655b163 100644 --- a/src/mcs/mcs_types.jl +++ b/src/mcs/mcs_types.jl @@ -159,7 +159,7 @@ mutable struct SimulationInstance{T} current_trial::Int current_data::Any # holds data for current_trial when current_trial > 0 sim_def::SimulationDef{T} where T <: AbstractSimulationData - models::Vector{Union{Model, MarginalModel}} + models::Vector{M} where M <: AbstractModel results::Vector{Dict{Tuple, DataFrame}} payload::Any @@ -172,7 +172,7 @@ mutable struct SimulationInstance{T} self.payload = deepcopy(self.sim_def.payload) # These are parallel arrays; each model has a corresponding results dict - self.models = Vector{Union{Model, MarginalModel}}(undef, 0) + self.models = Vector{AbstractModel}(undef, 0) self.results = [Dict{Tuple, DataFrame}()] return self diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index a8307f5f7..703cb159f 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -50,7 +50,7 @@ end # Store results for a single parameter and return the dataframe for this particular # trial/scenario -function _store_param_results(m::Union{Model, MarginalModel}, datum_key::Tuple{Symbol, Symbol}, trialnum::Int, scen_name::Union{Nothing, String}, results::Dict{Tuple, DataFrame}) +function _store_param_results(m::AbstractModel, datum_key::Tuple{Symbol, Symbol}, trialnum::Int, scen_name::Union{Nothing, String}, results::Dict{Tuple, DataFrame}) @debug "\nStoring trial results for $datum_key" (comp_name, datum_name) = datum_key @@ -395,7 +395,7 @@ end """ run(sim_def::SimulationDef{T}, - models::Union{Vector{Model}, Vector{MarginalModel}, Vector{Union{Model, MarginalModel}}, Model, MarginalModel}, + models::Union{Vector{M <: AbstractModel}, AbstractModel}, samplesize::Int; ntimesteps::Int=typemax(Int), trials_output_filename::Union{Nothing, AbstractString}=nothing, @@ -444,7 +444,7 @@ along with mutated information about trials, in addition to the model list and results information. """ function Base.run(sim_def::SimulationDef{T}, - models::Union{Vector{Model}, Vector{MarginalModel}, Vector{Any}, Model, MarginalModel}, + models::Union{Vector{M}, AbstractModel}, samplesize::Int; ntimesteps::Int=typemax(Int), trials_output_filename::Union{Nothing, AbstractString}=nothing, @@ -454,15 +454,15 @@ function Base.run(sim_def::SimulationDef{T}, scenario_func::Union{Nothing, Function}=nothing, scenario_placement::ScenarioLoopPlacement=OUTER, scenario_args=nothing, - results_in_memory::Bool=true) where T <: AbstractSimulationData + results_in_memory::Bool=true) where {T <: AbstractSimulationData, M <: AbstractModel} # If the provided models list has both a Model and a MarginalModel, it will be a Vector{Any}, and needs to be converted if models isa Vector{Any} - models = convert(Vector{Union{Model, MarginalModel}}, models) + models = convert(Vector{AbstractModel}, models) end # Quick check for results saving - if (!results_in_memory) && (results_output_dir===nothing) + if (!results_in_memory) && (results_output_dir === nothing) error("The results_in_memory keyword arg is set to ($results_in_memory) and results_output_dir keyword arg is set to ($results_output_dir), thus results will not be saved either in memory or in a file.") @@ -596,22 +596,22 @@ end # Set models """ - set_models!(sim_inst::SimulationInstance{T}, models::Union{Vector{Model}, Vector{MarginalModel}, Vector{Union{Model, MarginalModel}}}) + set_models!(sim_inst::SimulationInstance{T}, models::Union{Vector{M <: AbstractModel}}) Set the `models` to be used by the SimulationDef held by `sim_inst`. """ -function set_models!(sim_inst::SimulationInstance{T}, models::Union{Vector{Model}, Vector{MarginalModel}, Vector{Union{Model, MarginalModel}}}) where T <: AbstractSimulationData +function set_models!(sim_inst::SimulationInstance{T}, models::Vector{M}) where {T <: AbstractSimulationData, M <: AbstractModel} sim_inst.models = models _reset_results!(sim_inst) # sets results vector to same length end # Convenience methods for single model and MarginalModel """ -set_models!(sim_inst::SimulationInstance{T}, m::Union{Model, MarginalModel}) +set_models!(sim_inst::SimulationInstance{T}, m::AbstractModel) Set the model `m` to be used by the Simulatoin held by `sim_inst`. """ -set_models!(sim_inst::SimulationInstance{T}, m::Union{Model, MarginalModel}) where T <: AbstractSimulationData = set_models!(sim_inst, [m]) +set_models!(sim_inst::SimulationInstance{T}, m::AbstractModel) where T <: AbstractSimulationData = set_models!(sim_inst, [m]) # diff --git a/src/utils/getdataframe.jl b/src/utils/getdataframe.jl index 541b9aa25..fc2e6f743 100644 --- a/src/utils/getdataframe.jl +++ b/src/utils/getdataframe.jl @@ -128,14 +128,14 @@ end """ - getdataframe(m::Union{Model, MarginalModel}, comp_name::Symbol, pairs::Pair{Symbol, Symbol}...) + getdataframe(m::AbstractModel, comp_name::Symbol, pairs::Pair{Symbol, Symbol}...) Return a DataFrame with values for the given variables or parameters of model `m` indicated by `pairs`, where each pair is of the form `comp_name => item_name`. If more than one pair is provided, all must refer to items with the same dimensions, which are used to join the respective item values. """ -function getdataframe(m::Union{Model, MarginalModel}, pairs::Pair{Symbol, Symbol}...) +function getdataframe(m::AbstractModel, pairs::Pair{Symbol, Symbol}...) (comp_name1, item_name1) = pairs[1] dims = dim_names(m, comp_name1, item_name1) df = getdataframe(m, comp_name1, item_name1) @@ -153,26 +153,26 @@ function getdataframe(m::Union{Model, MarginalModel}, pairs::Pair{Symbol, Symbol end """ - getdataframe(m::Union{Model, MarginalModel}, pair::Pair{Symbol, NTuple{N, Symbol}}) + getdataframe(m::AbstractModel, pair::Pair{Symbol, NTuple{N, Symbol}}) Return a DataFrame with values for the given variables or parameters indicated by `pairs`, where each pair is of the form `comp_name => item_name`. If more than one pair is provided, all must refer to items with the same dimensions, which are used to join the respective item values. """ -function getdataframe(m::Union{Model, MarginalModel}, pair::Pair{Symbol, NTuple{N, Symbol}}) where N +function getdataframe(m::AbstractModel, pair::Pair{Symbol, NTuple{N, Symbol}}) where N comp_name = pair.first expanded = [comp_name => param_name for param_name in pair.second] return getdataframe(m, expanded...) end """ - getdataframe(m::Union{Model, MarginalModel}, comp_name::Symbol, item_name::Symbol) + getdataframe(m::AbstractModel, comp_name::Symbol, item_name::Symbol) Return the values for variable or parameter `item_name` in `comp_name` of model `m` as a DataFrame. """ -function getdataframe(m::Model, comp_name::Symbol, item_name::Symbol) +function getdataframe(m::AbstractModel, comp_name::Symbol, item_name::Symbol) if ! is_built(m) error("Cannot get DataFrame: model has not been built yet.") end From 07b7b80322928ec5132351262790dc50178aab6f Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Sat, 28 Sep 2019 16:14:00 -0700 Subject: [PATCH 78/81] Added some extra support for legacy models. --- src/core/types/params.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/types/params.jl b/src/core/types/params.jl index 6d361b289..eef2b8275 100644 --- a/src/core/types/params.jl +++ b/src/core/types/params.jl @@ -73,3 +73,8 @@ struct ExternalParameterConnection <: AbstractConnection param_name::Symbol # name of the parameter in the component external_param::Symbol # name of the parameter stored in external_params end + +# Converts symbol to component path +function ExternalParameterConnection(comp_name::Symbol, param_name::Symbol, external_param::Symbol) + return ExternalParameterConnection(ComponentPath(comp_name), param_name, external_param) +end \ No newline at end of file From a537915e707cd4b94f0bcf77529aeaae98755cb3 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Mon, 30 Sep 2019 14:09:35 -0700 Subject: [PATCH 79/81] Make label-checking optional. --- src/core/connections.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 48549df9f..f7ba08fa1 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -95,16 +95,18 @@ function _check_labels(obj::AbstractCompositeComponentDef, end """ - connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) + connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol; + check_labels::Bool=true) Connect a parameter `param_name` in the component `comp_name` of composite `obj` to the external parameter `ext_param_name`. """ -function connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol) +function connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol; + check_labels::Bool=true) comp_def = compdef(obj, comp_name) ext_param = external_param(obj, ext_param_name) - if ext_param isa ArrayModelParameter + if ext_param isa ArrayModelParameter && check_labels _check_labels(obj, comp_def, param_name, ext_param) end @@ -349,6 +351,7 @@ function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T comp_def = compdef(md, comp_path) comp_name = nameof(comp_def) + # @info "set_leftover_params: comp_name=$comp_name, param=$param_name" # check whether we need to set the external parameter if external_param(md, param_name, missing_ok=true) === nothing value = parameters[string(param_name)] From 43c5f003c740514b7b18a0262e2e749e9a6f6372 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Tue, 1 Oct 2019 09:16:30 -0700 Subject: [PATCH 80/81] Add funcs to smooth over difference between DatumDef and DatumDefReference objects --- src/core/defs.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index f3b6c8bec..2803c0140 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -371,7 +371,7 @@ has_parameter(comp_def::AbstractCompositeComponentDef, name::Symbol) = _ns_has(c function parameter_unit(obj::AbstractComponentDef, param_name::Symbol) param = parameter(obj, param_name) - return param.unit + return unit(param) end function parameter_dimensions(obj::AbstractComponentDef, param_name::Symbol) @@ -573,14 +573,19 @@ variable_names(comp_def::AbstractComponentDef) = [nameof(var) for var in variabl function variable_unit(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) var = variable(obj, comp_path, var_name) - return var.unit + return unit(var) end function variable_unit(obj::AbstractComponentDef, name::Symbol) var = variable(obj, name) - return var.unit + return unit(var) end +# Smooth over difference between VariableDef and VariableDefReference +unit(obj::AbstractDatumDef) = obj.unit +unit(obj::VariableDefReference) = variable(obj).unit +unit(obj::ParameterDefReference) = parameter(obj).unit + function variable_dimensions(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) var = variable(obj, comp_path, var_name) return dim_names(var) From 9fe54728356ef5f9a145323abeb0da34a6112e83 Mon Sep 17 00:00:00 2001 From: Richard Plevin Date: Fri, 4 Oct 2019 10:13:49 -0700 Subject: [PATCH 81/81] Update dependency on MimiFUND --- test/dependencies/run_dependency_tests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dependencies/run_dependency_tests.jl b/test/dependencies/run_dependency_tests.jl index 5833e46e1..79c6e2877 100644 --- a/test/dependencies/run_dependency_tests.jl +++ b/test/dependencies/run_dependency_tests.jl @@ -2,7 +2,7 @@ using Pkg packages_to_test = [ ("https://github.com/anthofflab/MimiRICE2010.jl.git", "v2.0.3", "MimiRICE2010"), - ("https://github.com/fund-model/MimiFUND.jl.git", "v3.11.5", "MimiFUND") + ("https://github.com/fund-model/MimiFUND.jl.git", "v3.11.8", "MimiFUND") ] for (pkg_url, pkg_rev, pkg_name) in packages_to_test