diff --git a/docs/src/internals/composite_components.md b/docs/src/internals/composite_components.md deleted file mode 100644 index e9a1ae545..000000000 --- a/docs/src/internals/composite_components.md +++ /dev/null @@ -1,183 +0,0 @@ -# Composite Components - -## Overview - -This document describes the core data structures used to implement in Mimi 1.0. - -Prior versions of Mimi supported only "flat" models, i.e., with one level of components. The current version supports mulitple layers of components, with some components being "final" or leaf components, and others being "composite" components which themselves contain other leaf or composite components. This approach allows for a cleaner organization of complex models, and allows the construction of building blocks that can be re-used in multiple models. - -To the degree possible, composite components are designed to operate the same as leaf components, though there are necessarily differences: - -1. Leaf components are defined using the macro `@defcomp`, while composites are defined using `@defcomposite`. Each macro supports syntax and semantics specific to the type of component. See below for more details on these macros. - -1. Leaf composites support user-defined `run_timestep()` functions, whereas composites have a built-in `run_timestep()` function that iterates over its subcomponents and calls their `run_timestep()` function. The `init()` function is handled analogously. - -### Classes.jl - -Most of the core data structures are defined using the `Classes.jl` package, which was developed for Mimi, but separated out as a generally useful julia package. The main features of `Classes` are: - -1. Classes can subclass other classes, thereby inheriting the same list of fields as a starting point, which can then be extended with further fields. - -1. A type hierarchy is defined automatically that allows classes and subclasses to be referenced with a single type. In short, if you define a class `Foo`, an abstract type called `AbstractFoo` is defined, along with the concrete class `Foo`. If you subclass `Foo` (say with the class `Bar`), then `AbstractBar` will be a subtype of `AbstractFoo`, allowing methods to be defined that operate on both the superclass and subclass. See the Classes.jl documentation for further details. - -For example, in Mimi, `ModelDef` is a subclass of `CompositeComponentDef`, which in turn is a subclass of `ComponentDef`. Thus, methods can be written with arguments typed `x::ComponentDef` to operate on leaf components only, or `x::AbstractCompositeComponentDef` to operate on composites and `ModelDef`, or as `x::AbstractComponentDef` to operate on all three concrete types. - -## Core types - -These are defined in `types/core.jl`. - -1. `MimiStruct` and `MimiClass` - -All structs and classes in Mimi are derived from these abstract types, which allows us to identify Mimi-defined items when writing `show()` methods. - -1. `ComponentId` - - To identify components, `@defcomp` creates a variable with the name of - the component whose value is an instance of this type. The definition is: - - ```julia - struct ComponentId <: MimiStruct - module_obj::Union{Nothing, Module} - comp_name::Symbol - end - ``` - -1. `ComponentPath` - - A `ComponentPath` identifies the path from one or more composites to any component, using an `NTuple` of symbols. Since component names are unique at the composite level, the sequence of names through a component hierarchy uniquely identifies a component in that hierarchy. - - ```julia - struct ComponentPath <: MimiStruct - names::NTuple{N, Symbol} where N - end - ``` - -## Model Definition - -Models are composed of two separate structures, which we refer to as the "definition" side and the "instance" or "instantiated" side. The definition side is operated on by the user via the `@defcomp` and `@defcomposite` macros, and the public API. - -The instantiated model can be thought of as a "compiled" version of the model definition, with its data structures oriented toward run-time efficiency. It is constructed by Mimi in the `build()` function, which is called by the `run()` function. - -The public API sets a flag whenever the user modifies the model definition, and the instance is rebuilt before it is run if the model definition has changed. Otherwise, the model instance is re-run. - -The model definition is constructed from the following elements. - -### Leaf components - -1. `DatumDef` - - This is a superclass holding elements common to `VariableDef` and `ParameterDef`, including the `ComponentPath` to the component in which the datum is defined, the data type, and dimension definitions. `DatumDef` subclasses are stored only in leaf components. - -1. `VariableDef <: DatumDef` - - This class adds no new fields. It exists to differentiate variables from parameters. - -1. `ParameterDef <: DatumDef` - - This class adds only a "default value" field to the `DatumDef`. Note that functions defined to operate on the `AbstractDatumDef` type work for both variable and parameter definitions. - -1. `ComponentDef` - -Instances of `ComponentDef` are defined using `@defcomp`. Their internal `namespace` -dictionary can hold both `VariableDef` and `ParameterDef` instances. - -### Composite components - -Composite components provide a single component-like interface to an arbitrarily complex set -of components (both leaf and composite components). - -1. `DatumReference` - - This abstract class serves as a superclass for `ParameterDefReference`, and - `VariableDefReference`. - -1. `ParameterDefReference`, and `VariableDefReference` - - These are used in composite components to store references to `ParameterDef` and `VariableDef` instances defined in leaf components. (They are conceptually like symbolic links in a - file system.) Whereas a `VariableDef` or `ParameterDef` can appear in a leaf - component, references to these may appear in any number of composite components. - - "Importing" a parameter or variable from a sub-component defines a reference to that - datum in a leaf component. Note that if a composite imports a datum from another - composite, a reference to the leaf datum is stored in each case. That is, we don't - store references to references. - -1. `CompositeComponentDef <: ComponentDef` - - Instances of `CompositeComponentDef` are defined using `@defcomposite`. Their internal `namespace` dictionary can hold instances of `ComponentDef`, `CompositeComponentDef`, `VariableDefReference` and `ParameterDefReference`. - Composite components also record internal parameter connections. - -1. `ModelDef <: CompositeComponentDef` - - A `ModelDef` is a top-level composite that also stores external parameters and a list - of external parameter connections. - -### Parameter Connections - -Parameters hold values defined exogneously to the model ("external" parameters) or to the -component ("internal" parameters). - -1. `InternalParameterConnection` - -Internal parameters are defined by connecting a parameter in one component to a variable -in another component. This struct holds the names and `ComponentPath`s of the parameter -and variable, and other information such as the "backup" data source. At build time, -internal parameter connections result in direct references from the parameter to the -storage allocated for the variable. - -1. `ExternalParameterConnection` - -Values that are exogenous to the model are defined in external parameters whose values are -assigned using the public API function `set_param!()`, or by setting default values in -`@defcomp` or `@defcomposite`, in which case, the default values are assigned via an -internal call to `set_param!()`. - -External connections are stored in the `ModelDef`, along with the actual `ModelParameter`s, -which may be scalar values or arrays, as described below. - -1. `ModelParameter` - -This is an abstract type that is the supertype of both `ScalarModelParameter{T}` and -`ArrayModelParameter{T}`. These two parameterized types are used to store values set -for external model parameters. - -## Instantiated Models - - - -1. `ComponentInstanceData` - -This is the supertype for variables and parameters in component instances. - -```julia -@class ComponentInstanceData{NT <: NamedTuple} <: MimiClass begin - nt::NT - comp_paths::Vector{ComponentPath} # records the origin of each datum -end -``` - -1. `ComponentInstanceParameters` - -1. `ComponentInstanceVariables` - -1. `ComponentInstance` - -1. `LeafComponentInstance <: ComponentInstance` - -1. `CompositeComponentInstance <: ComponentInstance` - - The `run_timestep()` method of a `ComponentInstance` simply calls the `run_timestep()` - method of each of its sub-components in dependency order. - -1. `ModelInstance <: CompositeComponentInstance` - - -## User-facing Classes - -1. `Model` - -The `Model` class contains the `ModelDef`, and after the `build()` function is called, a `ModelInstance` that can be run. The API for `Model` delegates many calls to either its top-level `ModeDef` or `ModelInstance`, while providing additional functionality including running a Monte Carlo simulation. - -1. `ComponentReference` - -1. `VariableReference` diff --git a/docs/src/internals/structures_1_overview.md b/docs/src/internals/structures_1_overview.md new file mode 100644 index 000000000..f493a0846 --- /dev/null +++ b/docs/src/internals/structures_1_overview.md @@ -0,0 +1,52 @@ + +# Overview + +This document (along with "structures_2_definitions" and "structures_3_instances") describes the core data structures used to implement in Mimi 1.0. + +Prior versions of Mimi supported only "flat" models, i.e., with one level of components. The current version supports mulitple layers of components, with some components being "final" or leaf components, and others being "composite" components which themselves contain other leaf or composite components. This approach allows for a cleaner organization of complex models, and allows the construction of building blocks that can be re-used in multiple models. + +To the degree possible, composite components are designed to operate the same as leaf components, though there are necessarily differences: + +1. Leaf components are defined using the macro `@defcomp`, while composites are defined using `@defcomposite`. Each macro supports syntax and semantics specific to the type of component. See below for more details on these macros. + +1. Leaf composites support user-defined `run_timestep()` functions, whereas composites have a built-in `run_timestep()` function that iterates over its subcomponents and calls their `run_timestep()` function. The `init()` function is handled analogously. + +## Classes.jl + +Most of the core data structures are defined using the `Classes.jl` package, which was developed for Mimi, but separated out as a generally useful julia package. The main features of `Classes` are: + +1. Classes can subclass other classes, thereby inheriting the same list of fields as a starting point, which can then be extended with further fields. + +1. A type hierarchy is defined automatically that allows classes and subclasses to be referenced with a single type. In short, if you define a class `Foo`, an abstract type called `AbstractFoo` is defined, along with the concrete class `Foo`. If you subclass `Foo` (say with the class `Bar`), then `AbstractBar` will be a subtype of `AbstractFoo`, allowing methods to be defined that operate on both the superclass and subclass. See the Classes.jl documentation for further details. + +For example, in Mimi, `ModelDef` is a subclass of `CompositeComponentDef`, which in turn is a subclass of `ComponentDef`. Thus, methods can be written with arguments typed `x::ComponentDef` to operate on leaf components only, or `x::AbstractCompositeComponentDef` to operate on composites and `ModelDef`, or as `x::AbstractComponentDef` to operate on all three concrete types. + +## Core types + +These are defined in `types/core.jl`. + +1. `MimiStruct` and `MimiClass` + +All structs and classes in Mimi are derived from these abstract types, which allows us to identify Mimi-defined items when writing `show()` methods. + +1. `ComponentId` + + To identify components, `@defcomp` creates a variable with the name of + the component whose value is an instance of this type. The definition is: + + ```julia + struct ComponentId <: MimiStruct + module_obj::Union{Nothing, Module} + comp_name::Symbol + end + ``` + +1. `ComponentPath` + + A `ComponentPath` identifies the path from one or more composites to any component, using an `NTuple` of symbols. Since component names are unique at the composite level, the sequence of names through a component hierarchy uniquely identifies a component in that hierarchy. + + ```julia + struct ComponentPath <: MimiStruct + names::NTuple{N, Symbol} where N + end + ``` \ No newline at end of file diff --git a/docs/src/internals/structures_2_definitions.md b/docs/src/internals/structures_2_definitions.md new file mode 100644 index 000000000..e9bd38b0f --- /dev/null +++ b/docs/src/internals/structures_2_definitions.md @@ -0,0 +1,137 @@ +# Definitions + +## Model Definition + +Models are composed of two separate structures, which we refer to as the "definition" side and the "instance" or "instantiated" side. The definition side is operated on by the user via the `@defcomp` and `@defcomposite` macros, and the public API (`add_comp!`, `set_param!`, `connect_param!`, etc.). + +The instantiated model can be thought of as a "compiled" version of the model definition, with its data structures oriented toward run-time efficiency. It is constructed by Mimi in the `build()` function, which is called by the `run()` function. + +The public API sets a flag whenever the user modifies the model definition, and the instance is rebuilt before it is run if the model definition has changed. Otherwise, the model instance is re-run. + +The model definition is constructed from the following elements. + +## Leaf components + +Leaf components are defined using the `@defcomp` macro which generates a component definition of the type `ComponentDef` which has the following fields: +``` +# ComponentDef +parent::Any +name::Symbol +comp_id::Union{Nothing, ComponentId} +comp_path::Union{Nothing, ComponentPath} +dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} +namespace::OrderedDict{Symbol, Any} +first::Union{Nothing, Int} +last::Union{Nothing, Int} +is_uniform::Bool +``` +The namespace of a leaf component can hold `ParameterDef`s and `VariableDef`s, both which are subclasses of `DatumDef` (see below for more details on these types). + +## Composite components + +Composite components are defined using the `@defcomposite` macro which generates a composite component definition of the type `CompositeComponentDef` which has the following fields, in addition to the fields of a `ComponentDef`: +``` +# CompositeComponentDef <: ComponentDef +internal_param_conns::Vector{InternalParameterConnection} +backups::Vector{Symbol} +sorted_comps::Union{Nothing, Vector{Symbol}} +``` +The namespace of a composite component can hold `CompositeParameterDef`s and`CompositeVariableDef`s, as well as `AbstractComponentDef`s (which can be other leaf or composite component definitions). + +## Datum definitions + +Note: we use "datum" to refer collectively to parameters and variables. Parameters are values that are fed into a component, and variables are values calculated by a component's `run_timestep` function. + +Datum are defined with the `@defcomp` and `@defcomposite` macros, and have the following fields: +``` +# DatumDef +name::Symbol +comp_path::Union{Nothing, ComponentPath} +datatype::DataType +dim_names::Vector{Symbol} +description::String +unit::String +``` +The only difference between a ParameterDef and a VariableDef is that parameters can have default values. +``` +# ParameterDef <: DatumDef +default::Any + +# VariableDef <: DatumDef +# (This class adds no new fields. It exists to differentiate variables from parameters.) +``` + +`CompositeParameterDef`s and `CompositeVariableDef`s are defined in the `@defcomposite` macro, and point to datum from their subcomponents. (Remember, composite components do not have `run_timestep` functions, so no values are actually calculated in a composite component.) Thus, `CompositeParameterDef`s and `CompositeVariableDef`s inherit all the fields from `ParameterDef`s and `VariableDef`s, and have an additional field to record which subcomponent(s)' datum they reference. +``` +# CompositeParameterDef <: ParameterDef +refs::Vector{UnnamedReference} + +# CompositeVariableDef <: VariableDef +ref::UnnamedReference +``` +Note: a `CompositeParameterDef` can reference multiple subcomponents' parameters, but a `CompositeVariableDef` can only reference a variable from one subcomponent. + +The reference(s) stored in `CompositeParameterDef`s and `CompositeVariableDef`s are of type `UnnamedReference`, which has the following fields: +``` +# UnnamedReference +comp_name::Symbol # name of the referenced subcomponent +datum_name::Symbol # name of the parameter or variable in the subcomponent's namespace +``` + +## ModelDef + +A `ModelDef` is a top-level composite that also stores external parameters and a list of external parameter connections. It contains the following additional fields: +``` +# ModelDef <: CompositeComponentDef +external_param_conns::Vector{ExternalParameterConnection} +external_params::Dict{Symbol, ModelParameter} +number_type::DataType +dirty::Bool +``` +Note: a ModelDef's namespace will only hold `AbstractComponentDef`s. + +## Parameter Connections + +Parameters hold values defined exogneously to the model ("external" parameters) or to the +component ("internal" parameters). + +`InternalParameterConnection` +Internal parameters are defined by connecting a parameter in one component to a variable +in another component. This struct holds the names and `ComponentPath`s of the parameter +and variable, and other information such as the "backup" data source. At build time, +internal parameter connections result in direct references from the parameter to the +storage allocated for the variable. + +`ExternalParameterConnection` +Values that are exogenous to the model are defined in external parameters whose values are +assigned using the public API function `set_param!()`, or by setting default values in +`@defcomp` or `@defcomposite`, in which case, the default values are assigned via an +internal call to `set_param!()`. + +External connections are stored in the `ModelDef`, along with the actual `ModelParameter`s, +which may be scalar values or arrays, as described below. + +``` +# AbstractConnection + +# 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 + +# ExternalParameterConnection <: AbstractConnection +comp_path::ComponentPath +param_name::Symbol # name of the parameter in the component +external_param::Symbol # name of the parameter stored in the model's external_params +``` + +## Model parameters + +`ModelParameter` +This is an abstract type that is the supertype of both `ScalarModelParameter{T}` and +`ArrayModelParameter{T}`. These two parameterized types are used to store values set +for external model parameters. diff --git a/docs/src/internals/structures_3_instances.md b/docs/src/internals/structures_3_instances.md new file mode 100644 index 000000000..61f7a132b --- /dev/null +++ b/docs/src/internals/structures_3_instances.md @@ -0,0 +1,49 @@ +# Instances + +## Models and Components + +``` +# ComponentInstance +comp_name::Symbol +comp_id::ComponentID +comp_path::ComponentPath (from top (model) down) +first::Int +last::Int + +# LeafComponentInstance <: ComponentInstance +variables::ComponentInstanceVariables +parameters::ComponentInstanceParameters +init::Union{Nothing, Function} +run_timestep::Union{Nothing, Function} + +# CompositeComponentInstance <: ComponentInstance +comps_dict::OrderedDict{Symbol, ComponentInstance} +pars_dict::OrderedDict{Symbol, Any} # not currently being used but, probably should for better datum access +vars_dict::OrderedDict{Symbol, Any} # ^ same + +# ModelInstance <: CompositeComponentInstance +md::ModelDef +``` + +## Datum + +``` +# ComponentInstanceParameters (only exist in leaf component instances) +nt::NamedTuple{Tuple{Symbol}, Tuple{Type}} # Type is either ScalarModelParameter (for scalar parameters) or TimestepArray (for array parameters) +comp_paths::Vector{ComponentPath} + +# ComponentInstanceVariables (only exist in leaf component instances) +nt::NamedTuple{Tuple{Symbol}, Tuple{Type}} # Type is either ScalarModelParameter (for scalar variables) or TimestepArray (for array variables) +comp_paths::Vector{ComponentPath} +``` +Note: in the `ComponentInstanceParameters`, the values stored in the named tuple point to the actual variable arrays in the other components for things that are internally connected, or to the actual value stored in the mi.md.external_params dictionary if it's an external parameter. (So I'm not sure what the component paths are there for, because the component path seems to always reference the current component, even if the parameter data tehcnically originates from a different component.) + +## User-facing Classes + +1. `Model` + +The `Model` class contains the `ModelDef`, and after the `build()` function is called, a `ModelInstance` that can be run. The API for `Model` delegates many calls to either its top-level `ModeDef` or `ModelInstance`, while providing additional functionality including running a Monte Carlo simulation. + +1. `ComponentReference` + +1. `VariableReference` diff --git a/src/core/build.jl b/src/core/build.jl index b504696c8..c6693f756 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -92,6 +92,100 @@ function _instantiate_vars(md::ModelDef) return vdict end +""" + _find_paths_and_names(obj::AbstractComponentDef, datum_name::Symbol) + +Recurses through sub components and finds the full path(s) to desired datum, and their +names at the leaf level. Returns a tuple (paths::Vector{ComponentPath}, datum_names::Vector{Symbol}) +""" +function _find_paths_and_names(obj::AbstractComponentDef, datum_name::Symbol) + + # Base case-- leaf component + if obj isa ComponentDef + return ([nothing], [datum_name]) + end + + datumdef = obj[datum_name] + if datumdef isa CompositeVariableDef + refs = [datumdef.ref] # CompositeVariableDef's can only point to one subcomponent + else + refs = datumdef.refs # ComposteParameterDef's can have multiple refs + end + + paths = [] + datum_names = [] + + for ref in refs + # Get the comp and datum's for the current ref + next_obj = obj[ref.comp_name] + next_datum_name = ref.datum_name + + # Recurse + sub_paths, sub_datum_names = _find_paths_and_names(next_obj, next_datum_name) + + # Append the paths, and save with datum_names + for (sp, dn) in zip(sub_paths, sub_datum_names) + push!(paths, ComponentPath(next_obj.name, sp)) + push!(datum_names, dn) + end + end + + return (paths, datum_names) +end + +""" + _get_leaf_level_ipcs(md::ModelDef, conn::InternalParameterConnection) + +Returns a vector of InternalParameterConnections that represent all of the connections at the leaf level +that need to be made under the hood as specified by `conn`. +""" +function _get_leaf_level_ipcs(md::ModelDef, conn::InternalParameterConnection) + + top_dst_path = conn.dst_comp_path + comp = find_comp(md, top_dst_path) + comp !== nothing || error("Can't find $(top_dst_path) from $(md.comp_id)") + par_sub_paths, param_names = _find_paths_and_names(comp, conn.dst_par_name) + param_paths = [ComponentPath(top_dst_path, sub_path) for sub_path in par_sub_paths] + + top_src_path = conn.src_comp_path + comp = find_comp(md, top_src_path) + comp !== nothing || error("Can't find $(top_src_path) from $(md.comp_id)") + var_sub_path, var_name = _find_paths_and_names(comp, conn.src_var_name) + var_path = ComponentPath(top_src_path, var_sub_path[1]) + + ipcs = [InternalParameterConnection(var_path, var_name[1], param_path, param_name, + conn.ignoreunits, conn.backup; offset=conn.offset) for (param_path, param_name) in + zip(param_paths, param_names)] + return ipcs +end + + +""" + _get_leaf_level_epcs(md::AbstractCompositeComponentDef, epc::ExternalParameterConnection) + +Returns a vector that has a new ExternalParameterConnections that represent all of the connections at the leaf level +that need to be made under the hood as specified by `epc`. +""" +function _get_leaf_level_epcs(md::ModelDef, epc::ExternalParameterConnection) + + comp = find_comp(md, epc.comp_path) + comp !== nothing || error("Can't find $(epc.comp_path) from $(md.comp_id)") + par_sub_paths, param_names = _find_paths_and_names(comp, epc.param_name) + + leaf_epcs = ExternalParameterConnection[] + external_param_name = epc.external_param + + top_path = epc.comp_path + + for (par_sub_path, param_name) in zip(par_sub_paths, param_names) + param_path = ComponentPath(top_path, par_sub_path) + epc = ExternalParameterConnection(param_path, param_name, external_param_name) + push!(leaf_epcs, epc) + end + + return leaf_epcs +end + # Collect all parameters with connections to allocated variable storage function _collect_params(md::ModelDef, var_dict::Dict{ComponentPath, Any}) @@ -104,19 +198,20 @@ function _collect_params(md::ModelDef, var_dict::Dict{ComponentPath, Any}) pdict = Dict{Tuple{ComponentPath, Symbol}, Any}() for conn in conns - ipc = dereferenced_conn(md, conn) - # @info "src_comp_path: $(ipc.src_comp_path)" - src_vars = var_dict[ipc.src_comp_path] - # @info "src_vars: $src_vars, name: $(ipc.src_var_name)" - var_value_obj = get_property_obj(src_vars, ipc.src_var_name) - pdict[(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)" + ipcs = _get_leaf_level_ipcs(md, conn) + src_vars = var_dict[ipcs[1].src_comp_path] + var_value_obj = get_property_obj(src_vars, ipcs[1].src_var_name) + for ipc in ipcs + pdict[(ipc.dst_comp_path, ipc.dst_par_name)] = var_value_obj + end end for epc in external_param_conns(md) param = external_param(md, epc.external_param) - pdict[(epc.comp_path, epc.param_name)] = (param isa ScalarModelParameter ? param : value(param)) - # @info "external conn: $(pathof(epc)).$(nameof(epc)) => $(param)" + leaf_level_epcs = _get_leaf_level_epcs(md, epc) + for leaf_epc in leaf_level_epcs + pdict[(leaf_epc.comp_path, leaf_epc.param_name)] = (param isa ScalarModelParameter ? param : value(param)) + end end # Make the external parameter connections for the hidden ConnectorComps. @@ -188,51 +283,24 @@ function _set_defaults!(md::ModelDef) not_set = unconnected_params(md) isempty(not_set) && return - d = Dict() - - function _store_defaults(obj) - if obj isa AbstractCompositeComponentDef - for ref in obj.defaults - d[(pathof(ref), nameof(ref))] = ref.default - end - else - for param in parameters(obj) - if param.default !== nothing - d[(pathof(obj), nameof(param))] = param.default - end - end - end - end - recurse(md, _store_defaults) - for ref in not_set - param = dereference(ref) - path = pathof(param) - name = nameof(param) - key = (path, name) - value = get(d, key, missing) - if value !== missing - # @info "Setting param :$name to default $value" - set_param!(md, name, value) - end + comp_name, par_name = ref.comp_name, ref.datum_name + pardef = md[comp_name][par_name] + default_value = pardef.default + default_value === nothing || set_param!(md, par_name, default_value) end end function _build(md::ModelDef) - # moved to build(m), below - # import_params!(md) # @info "_build(md)" add_connector_comps!(md) - # moved to build(m), below - # _set_defaults!(md) - # check if all parameters are set not_set = unconnected_params(md) if ! isempty(not_set) - params = join(not_set, "\n ") + params = join([p.datum_name for p in not_set], "\n ") error("Cannot build model; the following parameters are not set:\n $params") end @@ -253,8 +321,6 @@ function _build(md::ModelDef) end function build(m::Model) - # import any unconnected params into ModelDef - import_params!(m.md) # apply defaults to unset parameters _set_defaults!(m.md) diff --git a/src/core/connections.jl b/src/core/connections.jl index bef4bc52f..4a5071bf7 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -54,7 +54,7 @@ end verify_units(unit1::AbstractString, unit2::AbstractString) = (unit1 == unit2) function _check_labels(obj::AbstractCompositeComponentDef, - comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) + comp_def::AbstractComponentDef, param_name::Symbol, ext_param::ArrayModelParameter) param_def = parameter(comp_def, param_name) t1 = eltype(ext_param.values) @@ -111,7 +111,7 @@ function connect_param!(obj::AbstractCompositeComponentDef, comp_name::Symbol, connect_param!(obj, comp_def, param_name, ext_param_name, check_labels=check_labels) end -function connect_param!(obj::AbstractCompositeComponentDef, comp_def::ComponentDef, +function connect_param!(obj::AbstractCompositeComponentDef, comp_def::AbstractComponentDef, param_name::Symbol, ext_param_name::Symbol; check_labels::Bool=true) ext_param = external_param(obj, ext_param_name) @@ -278,94 +278,58 @@ function split_datum_path(obj::AbstractCompositeComponentDef, s::AbstractString) return (ComponentPath(obj, elts[1]), Symbol(elts[2])) end -# TBD: Deprecated? """ -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 - -""" - param_ref(md::ModelDef, conn::InternalParameterConnection) - -Return a ParameterDefReference to the parameter indicated in the given -connection. Used for comparing connections to parameters to find those -that are (un)connected. -""" -function param_ref(obj::AbstractCompositeComponentDef, conn::InternalParameterConnection) - comp = find_comp(obj, conn.dst_comp_path) - comp !== nothing || error("Can't find $(conn.dst_comp_path) from $(obj.comp_id)") - return datum_reference(comp, conn.dst_par_name) -end - -function param_ref(md::ModelDef, conn::ExternalParameterConnection) - comp = find_comp(md, pathof(conn)) - comp !== nothing || error("Can't find $(pathof(conn)) from $(md.comp_id)") - return datum_reference(comp, nameof(conn)) -end + connection_refs(obj::AbstractCompositeComponentDef) +Return a vector of UnnamedReference's to parameters from subcomponents that are either found in +internal connections or that have been already imported in a parameter definition. """ - dereferenced_conn(obj::AbstractCompositeComponentDef, - conn::InternalParameterConnection) +function connection_refs(obj::AbstractCompositeComponentDef) + refs = UnnamedReference[] -Convert an InternalParameterConnection with datum references to one referring -only to leaf component pars and vars. -""" -function dereferenced_conn(obj::AbstractCompositeComponentDef, - conn::InternalParameterConnection) - comp = find_comp(obj, conn.dst_comp_path) - comp !== nothing || error("Can't find $(conn.dst_comp_path) from $(obj.comp_id)") - pref = datum_reference(comp, conn.dst_par_name) + for conn in obj.internal_param_conns + push!(refs, UnnamedReference(conn.dst_comp_path.names[end], conn.dst_par_name)) + end - comp = find_comp(obj, conn.src_comp_path) - comp !== nothing || error("Can't find $(conn.src_comp_path) from $(obj.comp_id)") - vref = datum_reference(comp, conn.src_var_name) + for item in values(obj.namespace) + if item isa CompositeParameterDef + for ref in item.refs + push!(refs, ref) + end + end + end - ipc = InternalParameterConnection(pathof(vref), nameof(vref), pathof(pref), nameof(pref), - conn.ignoreunits, conn.backup; offset=conn.offset) - return ipc + return refs end """ - connection_refs(obj::AbstractCompositeComponentDef) + connection_refs(obj::ModelDef) -Return a vector of ParameterDefReferences to parameters with connections. -The references are always to the original Parameter definition in a leaf -component. +Return a vector of UnnamedReference's to parameters from subcomponents that are either found in +internal connections or that have been already connected to external parameter values. """ -function connection_refs(obj::AbstractCompositeComponentDef) - refs = ParameterDefReference[] - root = get_root(obj) - # @info "root of $(obj.comp_id) is $(root.comp_id))" +function connection_refs(obj::ModelDef) + refs = UnnamedReference[] - function _add_conns(obj::AbstractCompositeComponentDef) - append!(refs, [param_ref(root, conn) for conn in obj.internal_param_conns]) + for conn in obj.internal_param_conns + push!(refs, UnnamedReference(conn.dst_comp_path.names[end], conn.dst_par_name)) end - recurse(obj, _add_conns; composite_only=true) - - if obj isa ModelDef - append!(refs, [param_ref(obj, epc) for epc in external_param_conns(obj)]) + for conn in obj.external_param_conns + push!(refs, UnnamedReference(conn.comp_path.names[end], conn.param_name)) end + return refs end """ unconnected_params(obj::AbstractCompositeComponentDef) -Return a list of ParameterDefReferences to parameters that have not been connected +Return a list of UnnamedReference's to parameters that have not been connected to a value. """ function unconnected_params(obj::AbstractCompositeComponentDef) - return setdiff(leaf_params(obj), connection_refs(obj)) + return setdiff(subcomp_params(obj), connection_refs(obj)) end """ @@ -377,21 +341,26 @@ 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 for param_ref in unconnected_params(md) - comp_path = pathof(param_ref) - param_name = nameof(param_ref) - 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)] - param_dims = parameter_dimensions(md, comp_name, param_name) - - set_external_param!(md, param_name, value; param_dims = param_dims) - + param_name = param_ref.datum_name + comp_name = param_ref.comp_name + comp_def = find_comp(md, comp_name) + param_def = comp_def[param_name] + + # Only set the unconnected parameter if it doesn't have a default + if param_def.default === nothing + # check whether we need to create the external parameter + if external_param(md, param_name, missing_ok=true) === nothing + if haskey(parameters, string(param_name)) + value = parameters[string(param_name)] + param_dims = parameter_dimensions(md, comp_name, param_name) + + set_external_param!(md, param_name, value; param_dims = param_dims) + else + error("Cannot set parameter :$param_name, not found in provided dictionary and no default value detected.") + end + end + connect_param!(md, comp_name, param_name, param_name) end - connect_param!(md, comp_name, param_name, param_name) end nothing end diff --git a/src/core/defcomposite.jl b/src/core/defcomposite.jl index 86c7cdd66..2dfa31270 100644 --- a/src/core/defcomposite.jl +++ b/src/core/defcomposite.jl @@ -35,8 +35,6 @@ using MacroTools # 3. pname_ = Parameter(args__) # args can be: pname, comp.pname, or keyword=value # 4. connect(a.param, b.var) # -# -# @defcomposite should just emit all the same API calls one could make manually # # splitarg produces a tuple for each arg of the form (arg_name, arg_type, slurp, default) @@ -80,7 +78,7 @@ end # Convert @defcomposite "shorthand" statements into Mimi API calls # function _parse(expr) - valid_keys = (:default, :description, :visability, :unit) + valid_keys = (:default, :description, :unit) result = nothing if @capture(expr, newname_ = Component(compname_)) || @@ -190,59 +188,98 @@ macro defcomposite(cc_name, ex) end """ - import_params!(obj::AbstractCompositeComponentDef; - names::Union{Nothing,Vector{Symbol}}=nothing) + import_params!(obj::AbstractCompositeComponentDef) Imports all unconnected parameters below the given composite `obj` by adding references to these parameters in `obj`. -NOT IMPLEMENTED: If `names` is not `nothing`, only the given names ar imported. - -This is called automatically by `build!()`, but it can be useful for developers of -composites as well. - N.B. This is also called at the end of code emitted by @defcomposite. """ -function import_params!(obj::AbstractCompositeComponentDef; - names::Union{Nothing,Vector{Symbol}}=nothing) +function import_params!(obj::AbstractCompositeComponentDef) unconn = unconnected_params(obj) - params = parameters(obj) - - # remove imported params from list of unconnected params - unconn = setdiff(unconn, params) - # filter!(param_ref -> !(param_ref in params), unconn) - - # verify that all explicit names are importable - if names !== nothing - unconn_names = [nameof(param_ref) for param_ref in unconn] - unknown = setdiff(names, unconn_names) - if ! isempty(unknown) - @error "Can't import names $unknown as these are not unconnected params" - end - end + + # Check for unresolved parameter name collisions. + # Users must explicitly define any parameters that come from multiple subcomponents. + all_names = [ref.datum_name for ref in unconn] + unique_names = unique(all_names) + _map = Dict([name => count(isequal(name), all_names) for name in unique_names]) + non_unique = [name for (name, val) in _map if val>1] + isempty(non_unique) || error("Cannot build composite :$(obj.name). There are unresolved parameter name collisions from subcomponents for the following parameter names: $(join(non_unique, ", ")).") for param_ref in unconn - # @info "importing $param_ref to $(obj.comp_id)" - name = nameof(param_ref) - if names === nothing || name in names - obj[name] = param_ref + name = param_ref.datum_name + haskey(obj, name) && error("Cannot build composite :$(obj.name). Failed to auto-import parameter :$name from component :$(param_ref.comp_name), this name has already been defined in the composite component's namespace.") + obj[name] = CompositeParameterDef(obj, param_ref) + end +end + +# Helper function for finding any field collisions for parameters that want to be joined +function _find_collisions(fields, pairs::Vector{Pair{T, Symbol}}) where T + collisions = Symbol[] + + pardefs = [comp.namespace[param_name] for (comp, param_name) in pairs] + for f in fields + subcomponent_set = Set([getproperty(pardef, f) for pardef in pardefs]) + length(subcomponent_set) > 1 && push!(collisions, f) + end + + return collisions +end + +# `kwargs` contains the keywords specified by the user when defining the composite parameter in @defcomposite. +# If the user does not provide a value for one or any of the possible fields, this function looks at the fields +# of the subcomponents' parameters to use, but errors if any of them are in conflict. +# Note that :dim_names and :datatype can't be specified at the composite level, but must match from the subcomponents. +function _resolve_composite_parameter_kwargs(obj::AbstractCompositeComponentDef, kwargs::Dict{Symbol, Any}, pairs::Vector{Pair{T, Symbol}}, parname::Symbol) where T <: AbstractComponentDef + + fields = (:default, :description, :unit, :dim_names, :datatype) + collisions = _find_collisions(fields, pairs) + + # Create a new dictionary of resolved values to return + new_kwargs = Dict{Symbol, Any}() + + for f in fields + try + new_kwargs[f] = kwargs[f] # Get the user specified value for this field if there is one + catch e + # If the composite definition does not specify a value, then need to look to subcomponents and resolve or error + if f in collisions + error("Cannot build composite parameter :$parname, subcomponents have conflicting values for the \"$f\" field.") + else + compdef, curr_parname = pairs[1] + pardef = compdef[curr_parname] + new_kwargs[f] = getproperty(pardef, f) + end end end + + return new_kwargs end -# Return the local name of an already-imported parameter, or nothing if not found -function _find_param_ref(obj, dr) - for (name, param_ref) in param_dict(obj) - # @info "Comparing refs $param_ref == $dr" - if param_ref == dr - # @info "Found prior import to $dr named $name" - return name +# Helper function for detecting whether a specified datum has already been imported or connected +function _is_connected(obj::AbstractCompositeComponentDef, comp_name::Symbol, datum_name::Symbol) + for (k, item) in obj.namespace + if isa(item, AbstractCompositeParameterDef) + for ref in item.refs + if ref.comp_name == comp_name && ref.datum_name == datum_name + return true + end + end + elseif isa(item, AbstractCompositeVariableDef) + ref = item.ref + if ref.comp_name == comp_name && ref.datum_name == datum_name + return true + end end end - nothing + return false + + # cannot use the following, because all parameters haven't bubbled up yet + # return UnnamedReference(comp_name, datum_name) in unconnected_params(obj) end +# This function creates a CompositeParameterDef in the CompositeComponentDef obj function import_param!(obj::AbstractCompositeComponentDef, localname::Symbol, pairs::Pair...; kwargs...) @@ -252,35 +289,19 @@ function import_param!(obj::AbstractCompositeComponentDef, localname::Symbol, for (comp, pname) in pairs if comp == :* # wild card - @info "Got wildcard for param $pname (Not yet implemented)" + error("Got wildcard component specification (*) for param $pname (Not yet implemented)") else compname = nameof(comp) has_comp(obj, compname) || error("_import_param!: $(obj.comp_id) has no element named $compname") - newcomp = obj[compname] - - dr = datum_reference(newcomp, pname) - old_name = _find_param_ref(obj, dr) - - # TBD: :allow_overwrite is not yet passed from @defcomposite - key = :allow_overwrite - if old_name === nothing || (haskey(kwargs, key) && kwargs[key]) - # import the parameter from the given component - obj[localname] = dr = datum_reference(newcomp, pname) - # @info "import_param! created dr $dr" - else - error("Duplicate import of $dr as $localname, already imported as $old_name. ", - "To allow duplicates, use Parameter($(nameof(comp)).$pname; :$key=True)") - end - - if haskey(kwargs, :default) - root = get_root(obj) - ref = ParameterDefReference(pname, root, pathof(newcomp), kwargs[:default]) - save_default!(obj, ref) - end + _is_connected(obj, compname, pname) && + error("Duplicate import of $(comp.name).$pname") end end + new_kwargs = _resolve_composite_parameter_kwargs(obj, Dict{Symbol, Any}(kwargs), collect(pairs), localname) + + obj[localname] = CompositeParameterDef(localname, pathof(obj), collect(pairs), new_kwargs) end """ @@ -293,7 +314,7 @@ function _import_var!(obj::AbstractCompositeComponentDef, localname::Symbol, end comp = @or(find_comp(obj, path), error("$path not found from component $(obj.comp_id)")) - obj[localname] = datum_reference(comp, vname) + obj[localname] = CompositeVariableDef(localname, pathof(obj), comp, vname) end nothing diff --git a/src/core/defmodel.jl b/src/core/defmodel.jl index 4e722f47e..87eebcf54 100644 --- a/src/core/defmodel.jl +++ b/src/core/defmodel.jl @@ -14,66 +14,69 @@ Define a Mimi model. The following types of expressions are supported: 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 - result = :( - let calling_module = @__MODULE__, comp_mod_name = nothing, comp_mod_obj = 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_obj = calling_module) # nameof(calling_module)) - # TBD: This may still not be right: - : :(comp_mod_obj = getfield(calling_module, $(QuoteNode(comp_mod_name))))) - addexpr(expr) - - name = (alias === nothing ? comp_name : alias) - expr = :(add_comp!($model_name, Mimi.ComponentId(comp_mod_obj, $(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_) || - @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, lhs_ = rhs_) && @capture(lhs, comp_.name_) - (path, param_name) = parse_dotted_symbols(lhs) - expr = :(Mimi.set_param!($model_name, $path, $(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) + + @warn("@defmodel is deprecated.") + + # @capture(ex, elements__) + + # # @__MODULE__ is evaluated in calling module when macro is interpreted + # result = :( + # let calling_module = @__MODULE__, comp_mod_name = nothing, comp_mod_obj = 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_obj = calling_module) # nameof(calling_module)) + # # TBD: This may still not be right: + # : :(comp_mod_obj = getfield(calling_module, $(QuoteNode(comp_mod_name))))) + # addexpr(expr) + + # name = (alias === nothing ? comp_name : alias) + # expr = :(add_comp!($model_name, Mimi.ComponentId(comp_mod_obj, $(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_) || + # @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, lhs_ = rhs_) && @capture(lhs, comp_.name_) + # (path, param_name) = parse_dotted_symbols(lhs) + # expr = :(Mimi.set_param!($model_name, $path, $(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 e6b61c963..1a1e028ed 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -8,8 +8,6 @@ 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) = components(obj)[comp_name] @@ -52,11 +50,6 @@ end dirty!(md::ModelDef) = (md.dirty = true) -compname(dr::AbstractDatumReference) = dr.comp_path.names[end] - -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 function number_type(obj::AbstractCompositeComponentDef) @@ -117,10 +110,10 @@ istype(T::DataType) = (pair -> pair.second isa T) 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) +param_dict(obj::AbstractCompositeComponentDef) = filter(istype(CompositeParameterDef), obj.namespace) var_dict(obj::ComponentDef) = filter(istype(VariableDef), obj.namespace) -var_dict(obj::AbstractCompositeComponentDef) = filter(istype(VariableDefReference), obj.namespace) +var_dict(obj::AbstractCompositeComponentDef) = filter(istype(CompositeVariableDef), obj.namespace) """ parameters(comp_def::AbstractComponentDef) @@ -138,7 +131,7 @@ 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(obj::AbstractCompositeComponentDef) = values(filter(istype(CompositeVariableDef), obj.namespace)) variables(comp_id::ComponentId) = variables(compdef(comp_id)) @@ -175,32 +168,11 @@ function _save_to_namespace(comp::AbstractComponentDef, key::Symbol, value::Name comp.namespace[key] = value end -""" - 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. -""" -function datum_reference(comp::AbstractCompositeComponentDef, datum_name::Symbol) - _ns_get(comp, datum_name, AbstractDatumReference) -end - function Base.setindex!(comp::AbstractCompositeComponentDef, value::CompositeNamespaceElement, key::Symbol) _save_to_namespace(comp, key, value) end -# Leaf components store ParameterDefReference or VariableDefReference instances in the namespace +# Leaf components store DatumDef instances in the namespace function Base.setindex!(comp::ComponentDef, value::LeafNamespaceElement, key::Symbol) _save_to_namespace(comp, key, value) end @@ -346,20 +318,13 @@ parameter_names(md::ModelDef, comp_name::Symbol) = parameter_names(compdef(md, c parameter_names(comp_def::AbstractComponentDef) = collect(keys(param_dict(comp_def))) -parameter(obj::ComponentDef, name::Symbol) = _ns_get(obj, name, ParameterDef) - -parameter(obj::AbstractCompositeComponentDef, - name::Symbol) = dereference(_ns_get(obj, name, ParameterDefReference)) +parameter(obj::AbstractComponentDef, name::Symbol) = _ns_get(obj, name, AbstractParameterDef) parameter(obj::AbstractCompositeComponentDef, comp_name::Symbol, param_name::Symbol) = parameter(compdef(obj, comp_name), param_name) -parameter(dr::ParameterDefReference) = parameter(compdef(dr), nameof(dr)) - -has_parameter(comp_def::ComponentDef, name::Symbol) = _ns_has(comp_def, name, ParameterDef) - -has_parameter(comp_def::AbstractCompositeComponentDef, - name::Symbol) = _ns_has(comp_def, name, ParameterDefReference) +has_parameter(comp_def::AbstractComponentDef, name::Symbol) = _ns_has(comp_def, name, AbstractParameterDef) +has_parameter(md::ModelDef, name::Symbol) = haskey(md.external_params, name) function parameter_unit(obj::AbstractComponentDef, param_name::Symbol) param = parameter(obj, param_name) @@ -391,39 +356,6 @@ function parameter_dimensions(obj::AbstractComponentDef, comp_name::Symbol, para return parameter_dimensions(compdef(obj, comp_name), param_name) end -# -# TBD: handling for importing/joining a set of parameters into a composite -# -# """ -# new_set_param!(m::ModelDef, param_name::Symbol, value) - -# Search all model components, find all unbound parameters named `param_name`, create -# external parameter called `param_name`, set its value to `value`, and create -# connections from the external unbound parameters named `param_name` to the new -# external parameter. If a prior external parameter `param_name` is found, raise error. -# """ -# function new_set_param!(md::ModelDef, param_name::Symbol, value) -# comps = comps_with_unbound_param(md, param_name) -# new_set_param!(md, comps, param_name, value) -# end - -# function new_set_param!(md::ModelDef, comp_list::Vector{ComponentDef}, -# param_name::Symbol, value) -# pairs = [pathof(cd) => param_name for cd in comp_list] -# new_set_param!(md, pairs, param_name, value) -# end - -# function new_set_param!(md::ModelDef, pairs::Vector{Pair{ComponentPath, Symbol}}, -# ext_param_name::Symbol, value) -# if length(pairs) > 0 -# param = set_external_param!(md, ext_param_name, value) - -# for (comp_path, param_name) in pairs -# connect_param!(md, comp_path, param_name, ext_param_name) -# end -# end -# return nothing -# end """ Find and return a vector of tuples containing references to a ComponentDef and @@ -496,12 +428,19 @@ function recurse(obj::ComponentDef, f::Function, args...; nothing end -function leaf_params(obj::AbstractCompositeComponentDef) - params = [] - recurse(obj, x->append!(params, parameters(x)); leaf_only=true) - - root = get_root(obj) - return [ParameterDefReference(nameof(param), root, pathof(param)) for param in params] +# return UnnamedReference's for all subcomponents' parameters +function subcomp_params(obj::AbstractCompositeComponentDef) + params = UnnamedReference[] + for (name, sub_obj) in obj.namespace + if sub_obj isa AbstractComponentDef + for (subname, curr_obj) in sub_obj.namespace + if curr_obj isa AbstractParameterDef + push!(params, UnnamedReference(name, subname)) + end + end + end + end + return params end """ @@ -519,85 +458,75 @@ end """ set_param!(md::ModelDef, comp_path::ComponentPath, param_name::Symbol, - value_dict::Dict{Symbol, Any}, dims=nothing) + 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!(md::ModelDef, comp_name::Symbol, value_dict::Dict{Symbol, Any}, - param_name::Symbol, dims=nothing) + param_name::Symbol; dims=nothing) value = value_dict[param_name] - set_param!(md, comp_name, param_name, value, dims) + set_param!(md, comp_name, param_name, value, dims=dims) end -# May be deprecated -function set_param!(md::ModelDef, comp_path::ComponentPath, - param_name::Symbol, value, dims=nothing) - # @info "set_param!($(md.comp_id), $comp_path, $param_name, $value)" - comp_def = find_comp(md, comp_path) - @or(comp_def, error("Component with path $comp_path not found")) - # set_param!(comp.parent, nameof(comp), param_name, value, dims) - set_param!(md, comp_def, param_name, value, dims) +function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value; dims=nothing) + set_param!(md, comp_name, param_name, param_name, value, dims=dims) end -function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, value, dims=nothing) +function set_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol, value; dims=nothing) comp_def = compdef(md, comp_name) @or(comp_def, error("Top-level component with name $comp_name not found")) - set_param!(md, comp_def, param_name, value, dims) + set_param!(md, comp_def, param_name, ext_param_name, value, dims=dims) end -""" -Compare two items, each of which must be either a ParameterDef or ParameterDefReference, -to see if they refer to the same ultimate parameter. -""" -function _is_same_param(p1::Union{ParameterDef, ParameterDefReference}, - p2::Union{ParameterDef, ParameterDefReference}) - p1 = (p1 isa ParameterDefReference ? dereference(p1) : p1) - p2 = (p2 isa ParameterDefReference ? dereference(p2) : p2) - return p1 == p2 -end - -function set_param!(md::ModelDef, comp_def::AbstractComponentDef, param_name::Symbol, - value, dims=nothing) +function set_param!(md::ModelDef, comp_def::AbstractComponentDef, param_name::Symbol, ext_param_name::Symbol, value; dims=nothing) has_parameter(comp_def, param_name) || error("Can't find parameter :$param_name in component $(pathof(comp_def))") - if ! has_parameter(md, param_name) - import_param!(md, param_name, comp_def => param_name) - - elseif ! _is_same_param(md[param_name], comp_def[param_name]) - error("Can't import parameter :$param_name; ModelDef has another item with this name") + if has_parameter(md, ext_param_name) + error("Cannot set parameter :$ext_param_name, the model already has an external parameter with this name.", + " Use `update_param(m, param_name, value)` to change the value, or use ", + "`set_param(m, comp_name, param_name, unique_param_name, value)` to set a value for only this component.") end - set_param!(md, param_name, value, dims) + set_param!(md, param_name, value, dims = dims, comps = [comp_def], ext_param_name = ext_param_name) end """ - set_param!(md::ModelDef, param_name::Symbol, value, dims=nothing) + set_param!(md::ModelDef, param_name::Symbol, value; dims=nothing) -Set the value of a parameter exposed in `md` by following the ParameterDefReference. If -not found in the local namespace, import it if there is only one such parameter in md'same -children, and it is not yet bound. Otherwise raise an error. +Set the value of a parameter in all components of the model that have a parameter of +the specified name. -The `value` can by a scalar, an array, or a NamedAray. Optional argument 'dims' is a list +The `value` can by a scalar, an array, or a NamedAray. Optional keyword 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, param_name::Symbol, value, dims=nothing) +function set_param!(md::ModelDef, param_name::Symbol, value; dims=nothing, ignoreunits::Bool=false, comps=nothing, ext_param_name=nothing) # search immediate subcomponents for this parameter - found = [comp for (compname, comp) in components(md) if has_parameter(comp, param_name)] - - if ! has_parameter(md, param_name) - count = length(found) - if count >= 1 - comp = found[1] - # @info "Found one child with $param_name to auto-import: $(comp.comp_id)" - import_param!(md, param_name, comp => param_name) - - # elseif count > 1 - # # error("Can't set parameter :$param_name; found in multiple components") - else # count == 0 - error("Can't set parameter :$param_name; not found in ModelDef or children") + if comps === nothing + comps = [comp for (compname, comp) in components(md) if has_parameter(comp, param_name)] + end + + if ext_param_name === nothing + ext_param_name = param_name + end + + if isempty(comps) + error("Can't set parameter :$param_name; not found in ModelDef or children") + end + + # which fields to check for collisions in subcomponents + fields = ignoreunits ? (:dim_names, :datatype) : (:dim_names, :datatype, :unit) + collisions = _find_collisions(fields, [comp => param_name for comp in comps]) + if ! isempty(collisions) + if :unit in collisions + error("Cannot set parameter :$param_name in the model, components have conflicting values for the :unit field of this parameter. ", + "Call `set_param!` with optional keyword argument `ignoreunits = true` to override.") + else + spec = join(collisions, " and ") + error("Cannot set parameter :$param_name in the model, components have conflicting values for the $spec of this parameter. ", + "Set these parameters with separate calls to `set_param!(m, comp_name, param_name, unique_param_name, value)`.") end end @@ -609,13 +538,12 @@ function set_param!(md::ModelDef, param_name::Symbol, value, dims=nothing) check_parameter_dimensions(md, value, dims, param_name) end - param_ref = md[param_name] - comp_def = compdef(param_ref) - param = dereference(param_ref) - param_dims = dim_names(param) + comp_def = comps[1] # since we alread checked that the found comps have no conflicting fields in their parameter definitions, we can just use the first one for reference below + param_def = comp_def[param_name] + param_dims = param_def.dim_names num_dims = length(param_dims) - data_type = param.datatype + data_type = param_def.datatype dtype = Union{Missing, (data_type == Number ? number_type(md) : data_type)} if num_dims > 0 @@ -628,7 +556,7 @@ function set_param!(md::ModelDef, param_name::Symbol, value, dims=nothing) value_dims = length(size(value)) if num_dims != value_dims error("Mismatched data size for a set parameter call: dimension :$param_name", - " in $(pathof(comp_def)) has $num_dims dimensions; indicated value", + " in has $num_dims dimensions; indicated value", " has $value_dims dimensions.") end value = convert(Array{dtype, num_dims}, value) @@ -660,20 +588,23 @@ function set_param!(md::ModelDef, param_name::Symbol, value, dims=nothing) values = value end - set_external_array_param!(md, param_name, values, param_dims) + param = ArrayModelParameter(values, param_dims) + # Need to check the dimensions of the parameter data against each component before addeding it to the model's external parameters + for comp in comps + _check_labels(md, comp, param_name, param) + end + set_external_param!(md, ext_param_name, param) + else # scalar parameter case value = convert(dtype, value) - set_external_scalar_param!(md, param_name, value) + set_external_scalar_param!(md, ext_param_name, value) end # connect_param! calls dirty! so we don't have to - if length(found) == 1 - connect_param!(md, comp_def, nameof(param_ref), param_name) - else - for comp_def in found - connect_param!(md, comp_def, param_name, param_name) - end + for comp in comps + # Set check_labels=false because we already checked above before setting the param + connect_param!(md, comp, param_name, ext_param_name, check_labels=false) end nothing end @@ -683,7 +614,7 @@ end # variable(obj::ComponentDef, name::Symbol) = _ns_get(obj, name, VariableDef) -variable(obj::AbstractCompositeComponentDef, name::Symbol) = _ns_get(obj, name, VariableDefReference) +variable(obj::AbstractCompositeComponentDef, name::Symbol) = _ns_get(obj, name, CompositeVariableDef) variable(comp_id::ComponentId, var_name::Symbol) = variable(compdef(comp_id), var_name) @@ -694,13 +625,9 @@ function variable(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, return variable(comp_def, var_name) end -variable(dr::VariableDefReference) = variable(compdef(dr), nameof(dr)) - - - 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) +has_variable(comp_def::AbstractCompositeComponentDef, name::Symbol) = _ns_has(comp_def, name, CompositeVariableDef) """ variable_names(md::AbstractCompositeComponentDef, comp_name::Symbol) @@ -722,10 +649,7 @@ function variable_unit(obj::AbstractComponentDef, name::Symbol) 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 """ variable_dimensions(obj::AbstractCompositeComponentDef, comp_path::ComponentPath, var_name::Symbol) @@ -891,25 +815,6 @@ function time_contains(outer::Dimension, inner::Dimension) return outer_idx[1] <= inner_idx[1] && outer_idx[end] >= inner_idx[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 not have a root") - - if has_variable(comp_def, datum_name) - return VariableDefReference(datum_name, root, path) - end - - if has_parameter(comp_def, datum_name) - @info "Calling ParameterDefReference($datum_name, $(root.comp_id), $path)" - return ParameterDefReference(datum_name, root, path) - end - - error("Component $(comp_def.comp_id) does not have a data item named $datum_name") -end - """ propagate_time!(obj::AbstractComponentDef, t::Dimension) @@ -926,20 +831,6 @@ function propagate_time!(obj::AbstractComponentDef, t::Dimension) end end -# Save the default value for a parameter, which is applied, if needed, at build time. -function save_default!(obj::AbstractCompositeComponentDef, comp::AbstractComponentDef, - param::ParameterDef) - root = get_root(obj) - path = pathof(comp) - name = nameof(param) - save_default!(obj, ParameterDefReference(name, root, path, param.default)) -end - -function save_default!(obj::AbstractCompositeComponentDef, ref::ParameterDefReference) - push!(obj.defaults, ref) - nothing -end - """ add_comp!( obj::AbstractCompositeComponentDef, @@ -983,15 +874,6 @@ function add_comp!(obj::AbstractCompositeComponentDef, _add_anonymous_dims!(obj, comp_def) _insert_comp!(obj, comp_def, before=before, after=after) - # 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 - save_default!(obj, comp_def, param) - end - end - end - # Return the comp since it's a copy of what was passed in return comp_def end diff --git a/src/core/instances.jl b/src/core/instances.jl index 911bdc524..b5fa810de 100644 --- a/src/core/instances.jl +++ b/src/core/instances.jl @@ -67,7 +67,7 @@ end if T <: ScalarModelParameter return setproperty!(prop_obj, :value, value) else - error("You cannot override indexed variable $name::$T.") + error("You cannot override indexed variable $name::$T. Make sure you are using proper indexing syntax in the `run_timestep` function: v.varname[t] = ...") end end diff --git a/src/core/model.jl b/src/core/model.jl index 24ed666fc..6282cdba8 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -332,42 +332,34 @@ Delete a `component`` by name from a model `m`'s ModelDef, and nullify the Model @delegate Base.delete!(m::Model, comp_name::Symbol) => md """ - set_param!(m::Model, comp_name::Symbol, name::Symbol, value, dims=nothing) + set_param!(m::Model, comp_name::Symbol, param_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' +The `value` can by a scalar, an array, or a NamedAray. Optional keyword 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 -# Deprecated? """ - set_param!(m::Model, path::AbstractString, param_name::Symbol, value, dims=nothing) + set_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_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 - -# Deprecated? +Set the parameter `param_name` of a component `comp_name` in a model `m` to a given `value`, +storing the value in the model's external parameter list by the provided name `ext_param_name`. +The `value` can by a scalar, an array, or a NamedAray. Optional keyword 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. """ - set_param!(m::Model, path::AbstractString, value, dims=nothing) +@delegate set_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol, value; dims=nothing) => md -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_param!(m::Model, param_name::Symbol, value; dims=nothing) -Set the value of a parameter exposed in the ModelDef (m.md). +Set the value of a parameter in all components of the model that have a parameter of +the specified name. """ -@delegate set_param!(m::Model, param_name::Symbol, value, dims=nothing) => md - -# Deprecated? -@delegate set_param!(m::Model, comp_path::ComponentPath, param_name::Symbol, value, dims=nothing) => md +@delegate set_param!(m::Model, param_name::Symbol, value; dims=nothing, ignoreunits::Bool=false) => md @delegate import_params!(m::Model) => md diff --git a/src/core/paths.jl b/src/core/paths.jl index 184100a46..88c7e6d79 100644 --- a/src/core/paths.jl +++ b/src/core/paths.jl @@ -24,9 +24,8 @@ Base.joinpath(p1::ComponentPath, other...) = joinpath(joinpath(p1, other[1]), ot _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 connections, and for all -DatumReferences in the namespace. For leaf components, also update the ComponentPath -for ParameterDefs and VariableDefs. +For composites, also update the component paths for all connections. For leaf components, +also update the ComponentPath for ParameterDefs and VariableDefs. """ function _fix_comp_path!(child::AbstractComponentDef, parent::AbstractCompositeComponentDef) parent_path = pathof(parent) @@ -60,22 +59,6 @@ function _fix_comp_path!(child::AbstractComponentDef, parent::AbstractCompositeC offset=conn.offset) end - for (name, ref) in ns(child) - if ref isa AbstractDatumReference - T = typeof(ref) - # @info "parent_path: $parent_path ref.comp_path: $(ref.comp_path)" - ref_comp = find_comp(parent, pathof(ref)) - child[name] = new_ref = T(ref.name, root, pathof(ref_comp)) - # @info "new path: $(new_ref.comp_path)" - end - end - - defaults = child.defaults - for (i, ref) in enumerate(defaults) - ref_comp = find_comp(parent, pathof(ref)) - defaults[i] = ParameterDefReference(ref.name, root, pathof(ref_comp), ref.default) - end - else for datum in [variables(child)..., parameters(child)...] # @info "Resetting leaf IPC from $(datum.comp_path) to $child_path" @@ -88,9 +71,8 @@ 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. +This includes updating the component paths for all internal/external connections. For leaf components, +we also update the ComponentPath for ParameterDefs and VariableDefs. """ function fix_comp_paths!(md::AbstractModelDef) for child in compdefs(md) @@ -167,8 +149,6 @@ function find_comp(obj::AbstractCompositeComponentDef, pathstr::AbstractString) find_comp(obj, path) end -find_comp(dr::AbstractDatumReference) = find_comp(dr.root, dr.comp_path) - find_comp(cr::AbstractComponentReference) = find_comp(parent(cr), pathof(cr)) """ diff --git a/src/core/references.jl b/src/core/references.jl index 275281045..69264b353 100644 --- a/src/core/references.jl +++ b/src/core/references.jl @@ -2,18 +2,24 @@ set_param!(ref::ComponentReference, name::Symbol, value) Set a component parameter as `set_param!(reference, name, value)`. +This creates a unique name :compname_paramname in the model's external parameter list, +and sets the parameter only in the referenced component to that value. """ function set_param!(ref::ComponentReference, name::Symbol, value) - set_param!(parent(ref), pathof(ref), name, value) + compdef = find_comp(ref) + unique_name = Symbol("$(compdef.name)_$name") + set_param!(parent(ref), compdef, name, unique_name, value) end """ Base.setindex!(ref::ComponentReference, value, name::Symbol) -Set a component parameter as `reference[symbol] = value`. +Set a component parameter as `reference[name] = value`. +This creates a unique name :compname_paramname in the model's external parameter list, +and sets the parameter only in the referenced component to that value. """ function Base.setindex!(ref::ComponentReference, value, name::Symbol) - set_param!(parent(ref), pathof(ref), name, value) + set_param!(ref, name, value) end """ diff --git a/src/core/show.jl b/src/core/show.jl index c8667caab..d0fd44616 100644 --- a/src/core/show.jl +++ b/src/core/show.jl @@ -188,15 +188,6 @@ function show(io::IO, obj::AbstractComponentDef) end end -function show(io::IO, obj::VariableDefReference) - print(io, "VariableDefReference(name=:$(obj.name) path=$(obj.comp_path))") -end - -function show(io::IO, obj::ParameterDefReference) - default = printable(obj.default) - print(io, "ParameterDefReference(name=:$(obj.name) path=$(obj.comp_path) default=$default)") -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 7d86b8d7f..1e8921170 100644 --- a/src/core/types/core.jl +++ b/src/core/types/core.jl @@ -42,10 +42,13 @@ ComponentPath(names::Vector{Symbol}) = ComponentPath(Tuple(names)) ComponentPath(names::Vararg{Symbol}) = ComponentPath(Tuple(names)) ComponentPath(path::ComponentPath, name::Symbol) = ComponentPath(path.names..., name) +ComponentPath(name::Symbol, path::ComponentPath) = ComponentPath(name, path.names...) ComponentPath(path1::ComponentPath, path2::ComponentPath) = ComponentPath(path1.names..., path2.names...) ComponentPath(::Nothing, name::Symbol) = ComponentPath(name) +ComponentPath(name::Symbol, ::Nothing) = ComponentPath(name) +ComponentPath(path::ComponentPath, ::Nothing) = ComponentPath(path.names...) # Convert path string like "/foo/bar/baz" to ComponentPath(:foo, :bar, :baz) ComponentPath(path::AbstractString) = ComponentPath([Symbol(s) for s in split(path, "/") if s != ""]...) diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index 449c8db0e..edc00eeb5 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -10,8 +10,7 @@ end """ nameof(obj::NamedDef) = obj.name -Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, -`CompositeComponentDef`, and `VariableDefReference` and `ParameterDefReference`. +Return the name of `def`. `NamedDef`s include `DatumDef`, `ComponentDef`, and `CompositeComponentDef` """ Base.nameof(obj::AbstractNamedObj) = obj.name @@ -103,66 +102,55 @@ struct SubComponent <: MimiStruct alias::Union{Nothing, Symbol} 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 - # name::Symbol is inherited from NamedObj - root::AbstractComponentDef - comp_path::ComponentPath +# UnnamedReferences are stored in CompositeParameterDefs or CompositeVariableDefs +# to point to subcomponents' parameters or variables. +struct UnnamedReference + comp_name::Symbol # name of the referenced subcomponent + datum_name::Symbol # name of the parameter or variable in the subcomponent's namespace end -Base.pathof(dr::AbstractDatumReference) = dr.comp_path +@class CompositeParameterDef <: ParameterDef begin + refs::Vector{UnnamedReference} +end -# -# TBD: rather than store an array of these, perhaps this class should store -# multiple references, since there is only one name and (at most) one default -# value. It might look like this: -# -# @class DatumReference <: NamedObj -# -# struct UnnamedReference -# root::AbstractComponentDef -# comp_path::ComponentPath -# end -# -# @class ParameterDefReference <: DatumReference begin -# name::Symbol is inherited from NamedObj -# default::Any # allows defaults set in composites -# refs::Vector{UnnammedReference} -# end -# -# DatumReference would become simpler, with no new fields beyond those in NamedObj, -# with the root and comp_path moved into VariableDefReference instead. -# -# @class VariableDefReference <: DatumReference begin -# name::Symbol is inherited from NamedObj -# unnamed_ref::UnnamedReference -# end -# +# Create a CompositeParameterDef from a list of compdefs/pnames +function CompositeParameterDef(name::Symbol, comp_path::ComponentPath, pairs::Vector{Pair{T, Symbol}}, kwargs) where T <: AbstractComponentDef + # Create the necessary references + refs = [UnnamedReference(nameof(comp), param_name) for (comp, param_name) in pairs] -@class ParameterDefReference <: DatumReference begin - default::Any # allows defaults set in composites -end + # Unpack the kwargs + datatype = kwargs[:datatype] + dim_names = kwargs[:dim_names] + description = kwargs[:description] + unit = kwargs[:unit] + default = kwargs[:default] -function ParameterDefReference(name::Symbol, root::AbstractComponentDef, - comp_path::ComponentPath) - return ParameterDefReference(name, root, comp_path, nothing) + return CompositeParameterDef(name, comp_path, datatype, dim_names, description, unit, default, refs) end -@class VariableDefReference <: DatumReference +# Create a CompositeParameterDef from one subcomponent's ParameterDef (used by import_params!) +function CompositeParameterDef(obj, param_ref) + subcomp_name = param_ref.comp_name + pname = param_ref.datum_name -function dereference(ref::AbstractDatumReference) - comp = find_comp(ref) - return comp[ref.name] + pardef = obj.namespace[subcomp_name].namespace[pname] + return CompositeParameterDef(pname, pathof(obj), pardef.datatype, pardef.dim_names, pardef.description, pardef.unit, pardef.default, [param_ref]) end -# Might not be useful -# convert(::Type{VariableDef}, ref::VariableDefReference) = dereference(ref) -# convert(::Type{ParameterDef}, ref::ParameterDefReference) = dereference(ref) +@class CompositeVariableDef <: VariableDef begin + ref::UnnamedReference +end + +function CompositeVariableDef(name::Symbol, comp_path::ComponentPath, subcomp::AbstractComponentDef, vname::Symbol) + vardef = subcomp.namespace[vname] + comp_name = subcomp.name + return CompositeVariableDef(name, comp_path, vardef.datatype, vardef.dim_names, vardef.description, vardef.unit, UnnamedReference(comp_name, vname)) +end # Define which types can appear in the namespace dict for leaf and composite compdefs global const LeafNamespaceElement = AbstractDatumDef -global const CompositeNamespaceElement = Union{AbstractComponentDef, AbstractDatumReference} +global const CompositeDatumDef = Union{AbstractCompositeParameterDef, AbstractCompositeVariableDef} +global const CompositeNamespaceElement = Union{AbstractComponentDef, CompositeDatumDef} global const NamespaceElement = Union{LeafNamespaceElement, CompositeNamespaceElement} @class mutable CompositeComponentDef <: ComponentDef begin @@ -171,8 +159,6 @@ global const NamespaceElement = Union{LeafNamespaceElement, CompositeNa # Names of external params that the ConnectorComps will use as their :input2 parameters. backups::Vector{Symbol} - # store default values at the composite level until ModelDef is built - defaults::Vector{ParameterDefReference} sorted_comps::Union{Nothing, Vector{Symbol}} @@ -188,7 +174,6 @@ global const NamespaceElement = Union{LeafNamespaceElement, CompositeNa self.comp_path = ComponentPath(self.name) self.internal_param_conns = Vector{InternalParameterConnection}() self.backups = Vector{Symbol}() - self.defaults = Vector{ParameterDefReference}() self.sorted_comps = nothing end end diff --git a/test/runtests.jl b/test/runtests.jl index ae8003585..79901aa3b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,9 @@ Electron.prep_test_env() @info("test_composite.jl") @time include("test_composite.jl") + @info("test_composite_parameters.jl") + @time include("test_composite_parameters.jl") + @info("test_main_variabletimestep.jl") @time include("test_main_variabletimestep.jl") diff --git a/test/test_composite.jl b/test/test_composite.jl index bcedeb18b..7279399ac 100644 --- a/test/test_composite.jl +++ b/test/test_composite.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - ComponentId, ComponentPath, DatumReference, ComponentDef, AbstractComponentDef, + ComponentId, ComponentPath, ComponentDef, AbstractComponentDef, CompositeComponentDef, ModelDef, build, time_labels, compdef, find_comp, import_params! @@ -157,6 +157,8 @@ set_dimension!(m2, :time, 2005:2020) connect(Comp2.par_2_1, Comp1.var_1_1) connect(Comp2.par_2_2, Comp1.var_1_1) + + foo = Parameter(Comp1.foo, Comp2.foo) end top2_ref = add_comp!(m2, top2, nameof(top2)) diff --git a/test/test_composite_parameters.jl b/test/test_composite_parameters.jl new file mode 100644 index 000000000..da2a9f1c2 --- /dev/null +++ b/test/test_composite_parameters.jl @@ -0,0 +1,137 @@ +module TestCompositeParameters + +using Mimi +using Test + +@defcomp A begin + p1 = Parameter(unit = "\$", default=3) + p2 = Parameter() + + v1 = Variable() + v2 = Variable(index=[time]) +end + +@defcomp B begin + p1 = Parameter(unit = "thous \$", default=3) + p3 = Parameter(description="B p3") + p4 = Parameter(index=[time]) + +end + +#------------------------------------------------------------------------------ +# Test a failure to resolve namespace collisions + +fail_expr1 = :( + @defcomposite TestFailComposite begin + Component(A) + Component(B) + # Will fail because you need to resolve the namespace collision of the p1's + end +) + +err1 = try eval(fail_expr1) catch err err end +@test occursin("unresolved parameter name collisions from subcomponents", sprint(showerror, err1)) + + +#------------------------------------------------------------------------------ +# Test a failure to resolve conflicting "description" fields + +fail_expr2 = :( + @defcomposite TestFailComposite begin + Component(A) + Component(B) + + p1 = Parameter(A.p1, B.p1, unit="") + p2 = Parameter(A.p2, B.p3) # Will fail because the conflicting descriptions aren't resolved + end +) + +err2 = try eval(fail_expr2) catch err err end +@test occursin("subcomponents have conflicting values for the \"description\" field", sprint(showerror, err2)) + + +#------------------------------------------------------------------------------ +# Test trying to join parameters with different dimensions + +fail_expr3 = :( + @defcomposite TestFailComposite begin + Component(A) + Component(B) + + p1 = Parameter(A.p1, B.p1, unit="") + p2 = Parameter(A.p2, B.p4) # Will fail because different dimensions + end +) + +err3 = try eval(fail_expr3) catch err err end +@test occursin("subcomponents have conflicting values for the \"dim_names\" field", sprint(showerror, err3)) + + +#------------------------------------------------------------------------------ +# Test a failure to auto-import a paramter because it's name has already been used + +fail_expr4 = :( + @defcomposite TestFailComposite begin + Component(A) + Component(B) + + p3 = Parameter(A.p1, B.p1, unit="") # should fail on auto import of other p3 parameter + end +) + +err4 = try eval(fail_expr4) catch err err end +@test occursin("this name has already been defined in the composite component's namespace", sprint(showerror, err4)) + + +#------------------------------------------------------------------------------ +# Test an attempt to import a parameter twice + +fail_expr5 = :( + @defcomposite TestFailComposite begin + Component(A) + Component(B) + + p1 = Parameter(A.p1, B.p1, unit="") + p1_repeat = Parameter(B.p1) # should not allow a second import of B.p1 (already connected) + end +) + +err5 = try eval(fail_expr5) catch err err end +@test occursin("Duplicate import", sprint(showerror, err5)) + + +#------------------------------------------------------------------------------ +# Test set_param! with unit collision + +function get_model() + m = Model() + set_dimension!(m, :time, 10) + add_comp!(m, A) + add_comp!(m, B) + return m +end + +m1 = get_model() +err6 = try set_param!(m1, :p1, 5) catch err err end +@test occursin("components have conflicting values for the :unit field of this parameter", sprint(showerror, err6)) + +# use ignoreunits flag +set_param!(m1, :p1, 5, ignoreunits=true) + +err7 = try run(m1) catch err err end +@test occursin("Cannot build model; the following parameters are not set", sprint(showerror, err7)) + +# Set separate values for p1 in A and B +m2 = get_model() +set_param!(m2, :A, :p1, 1) # Set the value only for component A +@test length(m2.md.external_param_conns) == 1 # test that only one connection has been made +@test Mimi.UnnamedReference(:B, :p1) in Mimi.unconnected_params(m2.md) # and that B.p1 is still unconnected + +err8 = try set_param!(m2, :B, :p1, 2) catch err err end +@test occursin("the model already has an external parameter with this name", sprint(showerror, err8)) + +set_param!(m2, :B, :p1, :B_p1, 2) # Use a unique name to set B.p1 +@test length(m2.md.external_param_conns) == 2 +@test Set(keys(m2.md.external_params)) == Set([:p1, :B_p1]) + +end \ No newline at end of file diff --git a/test/test_composite_simple.jl b/test/test_composite_simple.jl index de1fa70c7..d1e1df23d 100644 --- a/test/test_composite_simple.jl +++ b/test/test_composite_simple.jl @@ -1,10 +1,10 @@ -#module TestCompositeSimple +module TestCompositeSimple using Test using Mimi import Mimi: - ComponentId, ComponentPath, DatumReference, ComponentDef, AbstractComponentDef, + ComponentId, ComponentPath, ComponentDef, AbstractComponentDef, CompositeComponentDef, ModelDef, build, time_labels, compdef, find_comp, import_params! @@ -51,14 +51,10 @@ md = m.md set_dimension!(m, :time, 2005:2020) add_comp!(m, Top) -top = md[:Top] set_param!(m, :Top, :fooA1, 10) - -import_params!(m) set_param!(m, :par_1_1, 1:16) -build(m) run(m) -#end +end diff --git a/test/test_composite_simple2.jl b/test/test_composite_simple2.jl deleted file mode 100644 index d6b23dee1..000000000 --- a/test/test_composite_simple2.jl +++ /dev/null @@ -1,58 +0,0 @@ -#module TestCompositeSimple - -using Test -using Mimi - -import Mimi: - ComponentId, ComponentPath, DatumReference, ComponentDef, AbstractComponentDef, - CompositeComponentDef, ModelDef, build, time_labels, compdef, find_comp, - import_params! - -@defcomp Leaf begin - p1 = Parameter() - v1 = Variable() - - function run_timestep(p, v, d, t) - v.v1 = p.p1 - end -end - -@defcomposite Intermediate begin - Component(Leaf) - v1 = Variable(Leaf.v1) -end - -@defcomposite Top begin - Component(Intermediate) - v = Variable(Intermediate.v1) - p = Parameter(Intermediate.p1) - connect(Intermediate.p1, Intermediate.v1) -end - - -m = Model() -md = m.md -set_dimension!(m, :time, 2005:2020) - -add_comp!(m, Top) - -top = md[:Top] -inter = top[:Intermediate] -leaf = inter[:Leaf] - -# -# Two ways to set a value in a subcomponent of the ModelDef: -# -use_import = false -if use_import - # import into model and reference it there - import_params!(m) - set_param!(m, :p, 10) -else - # or, reference :p in Top, - set_param!(m, :Top, :p, 10) -end - -build(m) -run(m) -#end diff --git a/test/test_getdataframe.jl b/test/test_getdataframe.jl index a1508606c..f0c020aed 100644 --- a/test/test_getdataframe.jl +++ b/test/test_getdataframe.jl @@ -43,6 +43,7 @@ set_param!(model1, :testcomp1, :par_scalar, 5.) add_comp!(model1, testcomp2) @test_throws ErrorException set_param!(model1, :testcomp2, :par2, late_first:5:early_last) +@test ! (:par2 in keys(model1.md.external_params)) # Test that after the previous error, the :par2 didn't stay in the model's parameter list set_param!(model1, :testcomp2, :par2, years) # Test running before model built diff --git a/test/test_main.jl b/test/test_main.jl index b33a00493..970d1c15e 100644 --- a/test/test_main.jl +++ b/test/test_main.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - reset_variables, @defmodel, + reset_variables, variable, variable_names, external_param, build, compdefs, dimension, compinstance @@ -25,19 +25,13 @@ import Mimi: var5 = Variable(index=[index1, idx4]) end - -@defmodel x1 begin - index[index1] = [:r1, :r2, :r3] - index[time] = 2010:10:2030 - index[idx3] = 1:3 - index[idx4] = 1:4 - component(foo1) - - foo1.par1 = 5.0 -end - -# 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)) +x1 = Model() +set_dimension!(x1, :index1, [:r1, :r2, :r3]) +set_dimension!(x1, :time, 2010:10:2030) +set_dimension!(x1, :idx3, 1:3) +set_dimension!(x1, :idx4, 1:4) +add_comp!(x1, foo1) +set_param!(x1, :foo1, :par1, 5.0) @test length(dimension(x1.md, :index1)) == 3 @@ -46,7 +40,7 @@ end par1 = external_param(x1, :par1) @test par1.value == 5.0 -set_param!(x1, :foo1, :par1, 6.0) +update_param!(x1, :par1, 6.0) par1 = external_param(x1, :par1) @test par1.value == 6.0 diff --git a/test/test_main_variabletimestep.jl b/test/test_main_variabletimestep.jl index c374e95c2..31ab5bbc1 100644 --- a/test/test_main_variabletimestep.jl +++ b/test/test_main_variabletimestep.jl @@ -4,7 +4,7 @@ using Test using Mimi import Mimi: - reset_variables, @defmodel, + reset_variables, variable, variable_names, external_param, build, compdef, compdefs, dimension, compinstance @@ -25,19 +25,13 @@ import Mimi: var5 = Variable(index=[index1, idx4]) end -@defmodel x1 begin - index[index1] = [:r1, :r2, :r3] - index[time] = [2010, 2015, 2030] - index[idx3] = 1:3 - index[idx4] = 1:4 - component(foo1) - - foo1.par1 = 5.0 - # foo1.par2 = [] -end - -# 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)) +x1 = Model() +set_dimension!(x1, :index1, [:r1, :r2, :r3]) +set_dimension!(x1, :time, [2010, 2015, 2030]) +set_dimension!(x1, :idx3, 1:3) +set_dimension!(x1, :idx4, 1:4) +add_comp!(x1, foo1) +set_param!(x1, :foo1, :par1, 5.0) @test length(dimension(x1.md, :index1)) == 3 @@ -46,7 +40,7 @@ end par1 = external_param(x1, :par1) @test par1.value == 5.0 -set_param!(x1, :foo1, :par1, 6.0) +update_param!(x1, :par1, 6.0) par1 = external_param(x1, :par1) @test par1.value == 6.0 diff --git a/test/test_marginal_models.jl b/test/test_marginal_models.jl index e730e9d7f..d40ab3512 100644 --- a/test/test_marginal_models.jl +++ b/test/test_marginal_models.jl @@ -23,7 +23,7 @@ set_param!(model1, :compA, :parA, x1) mm = MarginalModel(model1, .5) model2 = mm.marginal -set_param!(model2, :compA, :parA, x2) +update_param!(model2, :parA, x2) run(mm) @@ -34,7 +34,7 @@ end mm2 = create_marginal_model(model1, 0.5) mm2_marginal = mm2.marginal -set_param!(mm2_marginal, :compA, :parA, x2) +update_param!(mm2_marginal, :parA, x2) run(mm2) diff --git a/test/test_metainfo.jl b/test/test_metainfo.jl index 56521028f..7e311b51c 100644 --- a/test/test_metainfo.jl +++ b/test/test_metainfo.jl @@ -29,11 +29,10 @@ import Mimi: end end -@Mimi.defmodel test_model begin - index[time] = 2010:2100 - component(ch4forcing1) - component(ch4forcing1, ch4forcing2) # add another one with a different name -end +test_model = Model() +set_dimension!(test_model, :time, 2010:2100) +add_comp!(test_model, ch4forcing1) +add_comp!(test_model, ch4forcing1, :ch4forcing2) # add another one with a different name c0 = ch4forcing1 @test compmodule(c0) == TestMetaInfo diff --git a/test/test_metainfo_variabletimestep.jl b/test/test_metainfo_variabletimestep.jl index 8fe34fe42..3aa140abe 100644 --- a/test/test_metainfo_variabletimestep.jl +++ b/test/test_metainfo_variabletimestep.jl @@ -29,11 +29,10 @@ import Mimi: end end -@Mimi.defmodel test_model begin - index[time] = [2010:2100; 2105:5:2200] - component(ch4forcing1) - component(ch4forcing1, ch4forcing2) # add another one with a different name -end +test_model = Model() +set_dimension!(test_model, :time, [2010:2100; 2105:5:2200]) +add_comp!(test_model, ch4forcing1) +add_comp!(test_model, ch4forcing1, :ch4forcing2) # add another one with a different name c1 = compdef(test_model, :ch4forcing1) c2 = compdef(test_model, :ch4forcing2) diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index cba4c7f81..bdedb42b2 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -59,7 +59,8 @@ unconns = unconnected_params(m) @test length(unconns) == 1 c = compdef(m, :C) uconn = unconns[1] -@test (pathof(uconn), nameof(uconn)) == (c.comp_path, :parC) +@test uconn.comp_name == :C +@test uconn.datum_name == :parC connect_param!(m, :C => :parC, :B => :varB) diff --git a/test/test_model_structure_variabletimestep.jl b/test/test_model_structure_variabletimestep.jl index da9429f70..3c4ac70d3 100644 --- a/test/test_model_structure_variabletimestep.jl +++ b/test/test_model_structure_variabletimestep.jl @@ -67,7 +67,8 @@ unconns = unconnected_params(m) @test length(unconns) == 1 c = compdef(m, :C) uconn = unconns[1] -@test (pathof(uconn), nameof(uconn)) == (c.comp_path, :parC) +@test uconn.comp_name == :C +@test uconn.datum_name == :parC connect_param!(m, :C => :parC, :B => :varB) diff --git a/test/test_parameter_labels.jl b/test/test_parameter_labels.jl index 9ffcad632..71b597e8e 100644 --- a/test/test_parameter_labels.jl +++ b/test/test_parameter_labels.jl @@ -265,7 +265,7 @@ model3 = Model() set_dimension!(model3, :time, collect(2015:5:2110)) set_dimension!(model3, :regions, ["Region1", "Region2", "Region3"]) add_comp!(model3, compA) -set_param!(model3, :compA, :x, x, [:time, :regions]) +set_param!(model3, :compA, :x, x, dims = [:time, :regions]) run(model3) for t in 1:length(time_labels) diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index 18ad8610a..862603327 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -259,4 +259,66 @@ update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0), update_timesteps=true) @test external_param(m.md, :y).values == [10.,20.] @test external_param(m.md, :z).value == 0 + +#------------------------------------------------------------------------------ +# Test the three different set_param! methods for a Symbol type parameter +#------------------------------------------------------------------------------ + + +@defcomp A begin + p1 = Parameter{Symbol}() +end + +function _get_model() + m = Model() + set_dimension!(m, :time, 10) + add_comp!(m, A) + return m +end + +# Test the 3-argument version of set_param! +m = _get_model() +@test_throws MethodError set_param!(m, :p1, 3) # Can't set it with an Int + +set_param!(m, :p1, :foo) # Set it with a Symbol +run(m) +@test m[:A, :p1] == :foo + +# Test the 4-argument version of set_param! +m = _get_model() +@test_throws MethodError set_param!(m, :A, :p1, 3) + +set_param!(m, :A, :p1, :foo) +run(m) +@test m[:A, :p1] == :foo + +# Test the 5-argument version of set_param! +m = _get_model() +@test_throws MethodError set_param!(m, :A, :p1, :A_p1, 3) + +set_param!(m, :A, :p1, :A_p1, :foo) +run(m) +@test m[:A, :p1] == :foo + +#------------------------------------------------------------------------------ +# Test that if set_param! errors in the connection step, +# the created param doesn't remain in the model's list of params +#------------------------------------------------------------------------------ + +@defcomp A begin + p1 = Parameter(index = [time]) +end + +@defcomp B begin + p1 = Parameter(index = [time]) +end + +m = Model() +set_dimension!(m, :time, 10) +add_comp!(m, A) +add_comp!(m, B) + +@test_throws ErrorException set_param!(m, :p1, 1:5) # this will error because the provided data is the wrong size +@test isempty(m.md.external_params) # But it should not be added to the model's dictionary + end #module diff --git a/test/test_references.jl b/test/test_references.jl index 7a753321b..7b13b0595 100644 --- a/test/test_references.jl +++ b/test/test_references.jl @@ -3,39 +3,39 @@ module TestReferences using Test using Mimi -import Mimi: - getproperty, @defmodel - -@defcomp Foo begin - input = Parameter() - intermed = Variable(index=[time]) - +@defcomp A begin + p1 = Parameter() + v1 = Variable(index = [time]) function run_timestep(p, v, d, t) - v.intermed[t] = p.input + v.v1[t] = gettime(t) end end - -@defcomp Bar begin - intermed = Parameter(index=[time]) - output = Variable(index=[time]) - - function run_timestep(p, v, d, t) - v.output[t] = p.intermed[t] - end + +@defcomp B begin + p1 = Parameter() + p2 = Parameter(index = [time]) end +m = Model() +set_dimension!(m, :time, 10) +refA = add_comp!(m, A, :foo) +refB = add_comp!(m, B) -@defmodel m begin - index[time] = [1, 2] - component(Foo) - component(Bar) +refA[:p1] = 3 # creates a parameter specific to this component, with name "foo_p1" +@test length(m.md.external_param_conns) == 1 +@test Mimi.UnnamedReference(:B, :p1) in Mimi.unconnected_params(m.md) +@test :foo_p1 in keys(m.md.external_params) - Foo.input = 3.14 - Foo.intermed => Bar.intermed -end +refB[:p1] = 5 +@test length(m.md.external_param_conns) == 2 +@test :B_p1 in keys(m.md.external_params) -run(m) +# Use the ComponentReferences to make an internal connection +refB[:p2] = refA[:v1] -@test m[:Bar, :output][1] == 3.14 +run(m) +@test m[:foo, :p1] == 3 +@test m[:B, :p1] == 5 +@test m[:B, :p2] == collect(1:10) end \ No newline at end of file diff --git a/test/test_tools.jl b/test/test_tools.jl index ddcd1b50b..c61ce04da 100644 --- a/test/test_tools.jl +++ b/test/test_tools.jl @@ -13,40 +13,9 @@ import Mimi: @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 -final = 10 # N.B. ERROR: cannot assign variable Base.last from module Main +stepsize = 2 +final = 10 ts = 10 @test Mimi.interpolate(collect(0:stepsize:final), ts) == collect(0:stepsize/ts:final) -@defcomp Foo begin - input = Parameter() - intermed = Variable(index=[time]) - - function run_timestep(p, v, d, t) - v.intermed[t] = p.input - end -end - -@defcomp Bar begin - intermed = Parameter(index=[time]) - output = Variable(index=[time]) - - function run_timestep(p, v, d, t) - v.output[t] = p.intermed[t] - end -end - -m = Model() -set_dimension!(m, :time, 2) -foo = add_comp!(m, Foo) -bar = add_comp!(m, Bar) - -foo[:input] = 3.14 - -# The connection via references is broken... -# bar[:intermed] = m.md[:Foo][:intermed] -connect_param!(m, :Bar, :intermed, :Foo, :intermed) - -run(m) - end #module diff --git a/test/test_units.jl b/test/test_units.jl index 25f7996b1..d8e200391 100644 --- a/test/test_units.jl +++ b/test/test_units.jl @@ -3,7 +3,7 @@ module TestUnits using Test using Mimi -import Mimi: verify_units, connect_param!, ComponentReference, @defmodel +import Mimi: verify_units, connect_param!, ComponentReference # Try directly using verify_units @test verify_units("kg", "kg")