Line data Source code
1 : // Copyright 2024, UChicago Argonne, LLC
2 : // All Rights Reserved
3 : // Software Name: NEML2 -- the New Engineering material Model Library, version 2
4 : // By: Argonne National Laboratory
5 : // OPEN SOURCE LICENSE (MIT)
6 : //
7 : // Permission is hereby granted, free of charge, to any person obtaining a copy
8 : // of this software and associated documentation files (the "Software"), to deal
9 : // in the Software without restriction, including without limitation the rights
10 : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 : // copies of the Software, and to permit persons to whom the Software is
12 : // furnished to do so, subject to the following conditions:
13 : //
14 : // The above copyright notice and this permission notice shall be included in
15 : // all copies or substantial portions of the Software.
16 : //
17 : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 : // THE SOFTWARE.
24 :
25 : #include "neml2/models/ComposedModel.h"
26 :
27 : namespace neml2
28 : {
29 : register_NEML2_object(ComposedModel);
30 :
31 : OptionSet
32 2 : ComposedModel::expected_options()
33 : {
34 2 : OptionSet options = Model::expected_options();
35 2 : options.doc() =
36 : "Compose multiple models together to form a single model. The composed model can then be "
37 : "treated as a new model and composed with others. The [system documentation](@ref "
38 2 : "model-composition) provides in-depth explanation on how the models are composed together.";
39 :
40 2 : NonlinearSystem::enable_automatic_scaling(options);
41 :
42 4 : options.set<std::vector<std::string>>("models");
43 4 : options.set("models").doc() = "Models being composed together";
44 :
45 4 : options.set<std::vector<VariableName>>("additional_outputs");
46 6 : options.set("additional_outputs").doc() =
47 : "Extra output variables to be extracted from the composed model in addition to the ones "
48 2 : "identified through dependency resolution.";
49 :
50 4 : options.set<std::vector<std::string>>("priority");
51 6 : options.set("priority").doc() =
52 : "Priorities of models in decreasing order. A model with higher priority will be evaluated "
53 2 : "first. This is useful for breaking cyclic dependency.";
54 :
55 4 : options.set<bool>("automatic_nonlinear_parameter") = true;
56 4 : options.set("automatic_nonlinear_parameter").doc() =
57 2 : "Whether to automatically add dependent nonlinear parameters";
58 :
59 2 : return options;
60 0 : }
61 :
62 67 : ComposedModel::ComposedModel(const OptionSet & options)
63 : : Model(options),
64 134 : _additional_outputs(options.get<std::vector<VariableName>>("additional_outputs")),
65 201 : _auto_nl_param(options.get<bool>("automatic_nonlinear_parameter"))
66 : {
67 : // Each sub-model shall have _independent_ output storage. This is because the same model could
68 : // be registered as a sub-model by different models, and it could be evaluated with _different_
69 : // input, and hence yields _different_ output values.
70 305 : for (const auto & model_name : options.get<std::vector<std::string>>("models"))
71 238 : register_model(model_name, /*nonlinear=*/false, /*merge_input=*/false);
72 :
73 : // Each sub-model may have nonlinear parameters. In our design, nonlinear parameters _are_
74 : // models. Since we do not want to put the burden of "adding nonlinear parameters in the input
75 : // file through the option 'models'" on users, we should do more behind the scenes to register
76 : // them.
77 : //
78 : // Registering nonlinear parameters here ensures dependency resolution. And if a nonlinear
79 : // parameter is registered by multiple models (which is very possible), we won't have to
80 : // evaluate the nonlinar parameter over and over again!
81 67 : auto submodels = registered_models();
82 67 : if (_auto_nl_param)
83 238 : for (auto & submodel : submodels)
84 237 : for (auto && [pname, param] : submodel->named_nonlinear_parameters(/*recursive=*/true))
85 66 : if (std::find(_registered_models.begin(), _registered_models.end(), param.provider) ==
86 132 : _registered_models.end())
87 237 : _registered_models.push_back(param.provider);
88 :
89 : // Add registered models as nodes in the dependency resolver
90 304 : for (auto & submodel : registered_models())
91 237 : _dependency.add_node(submodel.get());
92 69 : for (const auto & var : _additional_outputs)
93 2 : _dependency.add_additional_outbound_item(var);
94 :
95 : // Define priority in the event of cyclic dependency
96 67 : auto priority_order = options.get<std::vector<std::string>>("priority");
97 67 : size_t priority = priority_order.empty() ? 0 : priority_order.size() - 1;
98 67 : for (const auto & model_name : priority_order)
99 0 : _dependency.set_priority(registered_model(model_name).get(), priority--);
100 :
101 : // Resolve the dependency
102 67 : _dependency.unique_item_provider() = true;
103 67 : _dependency.unique_item_consumer() = false;
104 67 : _dependency.resolve();
105 67 : const auto & resolution = _dependency.resolution();
106 :
107 : // Sort the registered models by dependency resolution
108 67 : std::vector<std::shared_ptr<Model>> sorted_models(resolution.size());
109 304 : for (std::size_t i = 0; i < resolution.size(); ++i)
110 237 : sorted_models[i] = registered_model(resolution[i]->name());
111 67 : _registered_models = std::move(sorted_models);
112 :
113 : // Register input variables
114 421 : for (const auto & item : _dependency.inbound_items())
115 354 : if (!input_axis().has_variable(item.value))
116 284 : clone_input_variable(item.parent->input_variable(item.value));
117 :
118 : // Register output variables
119 158 : for (const auto & item : _dependency.outbound_items())
120 91 : clone_output_variable(item.parent->output_variable(item.value));
121 :
122 : // Declare nonlinear parameters
123 238 : for (auto & submodel : submodels)
124 237 : for (auto && [pname, param] : submodel->named_nonlinear_parameters(/*recursive=*/true))
125 66 : if (input_axis().has_variable(param.provider_var))
126 171 : register_nonlinear_parameter(pname, param);
127 :
128 : // Check if this composed model defines values
129 67 : _defines_value = true;
130 304 : for (const auto & submodel : registered_models())
131 237 : if (!submodel->defines_values())
132 : {
133 0 : _defines_value = false;
134 0 : _defines_dvalue = false;
135 0 : _defines_d2value = false;
136 0 : break;
137 : }
138 :
139 : // Check if this composed model defines derivatives
140 67 : if (_defines_value)
141 : {
142 67 : _defines_dvalue = true;
143 304 : for (const auto & submodel : registered_models())
144 237 : if (!submodel->defines_derivatives())
145 : {
146 0 : _defines_dvalue = false;
147 0 : _defines_d2value = false;
148 0 : break;
149 : }
150 : }
151 :
152 : // Check if this composed model defines second derivatives
153 67 : if (_defines_dvalue)
154 : {
155 67 : _defines_d2value = true;
156 167 : for (const auto & submodel : registered_models())
157 146 : if (!submodel->defines_second_derivatives())
158 : {
159 46 : _defines_d2value = false;
160 46 : break;
161 : }
162 : }
163 :
164 : // Is JIT enabled?
165 67 : _jit = Model::is_jit_enabled(); // boolean read from input file
166 :
167 : // If any submodel does not support JIT, disable JIT
168 67 : if (_jit)
169 294 : for (const auto & submodel : registered_models())
170 231 : if (!submodel->is_jit_enabled())
171 : {
172 4 : _jit = false;
173 4 : break;
174 : }
175 67 : }
176 :
177 : std::map<std::string, NonlinearParameter>
178 32 : ComposedModel::named_nonlinear_parameters(bool /*recursive*/) const
179 : {
180 32 : return _nl_params;
181 : }
182 :
183 : void
184 237 : ComposedModel::link_input_variables(Model * submodel)
185 : {
186 2164 : for (const auto & item : _dependency.inbound_items())
187 1927 : if (item.parent == submodel)
188 354 : submodel->input_variable(item.value).ref(input_variable(item.value));
189 :
190 1639 : for (const auto & [item, providers] : _dependency.item_providers())
191 1402 : if (item.parent == submodel)
192 : {
193 184 : auto * depmodel = providers.begin()->parent;
194 184 : submodel->input_variable(item.value).ref(depmodel->output_variable(item.value));
195 : }
196 237 : }
197 :
198 : void
199 237 : ComposedModel::link_output_variables(Model * submodel)
200 : {
201 619 : for (const auto & item : _dependency.outbound_items())
202 382 : if (item.parent == submodel)
203 91 : output_variable(item.value).ref(submodel->output_variable(item.value));
204 237 : }
205 :
206 : void
207 183 : ComposedModel::set_value(bool out, bool dout_din, bool d2out_din2)
208 : {
209 790 : for (const auto & i : registered_models())
210 : {
211 607 : if (out && !dout_din && !d2out_din2)
212 345 : i->forward_maybe_jit(true, false, false);
213 262 : else if (dout_din && !d2out_din2)
214 214 : i->forward_maybe_jit(true, true, false);
215 48 : else if (d2out_din2)
216 48 : i->forward_maybe_jit(true, true, true);
217 : else
218 0 : throw NEMLException("Unsupported call signature to set_value");
219 : }
220 :
221 183 : if (dout_din)
222 137 : for (auto && [name, var] : output_variables())
223 75 : var->apply_chain_rule(_dependency);
224 :
225 183 : if (d2out_din2)
226 32 : for (auto && [name, var] : output_variables())
227 16 : var->apply_second_order_chain_rule(_dependency);
228 183 : }
229 : } // namespace neml2
|