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 : #include "neml2/base/Settings.h"
27 :
28 : namespace neml2
29 : {
30 : register_NEML2_object(ComposedModel);
31 :
32 : OptionSet
33 2 : ComposedModel::expected_options()
34 : {
35 2 : OptionSet options = Model::expected_options();
36 2 : options.doc() =
37 : "Compose multiple models together to form a single model. The composed model can then be "
38 : "treated as a new model and composed with others. The [system documentation](@ref "
39 2 : "model-composition) provides in-depth explanation on how the models are composed together.";
40 :
41 2 : NonlinearSystem::enable_automatic_scaling(options);
42 :
43 4 : options.set<std::vector<std::string>>("models");
44 4 : options.set("models").doc() = "Models being composed together";
45 :
46 4 : options.set<std::vector<VariableName>>("additional_outputs");
47 6 : options.set("additional_outputs").doc() =
48 : "Extra output variables to be extracted from the composed model in addition to the ones "
49 2 : "identified through dependency resolution.";
50 :
51 4 : options.set<std::vector<std::string>>("priority");
52 6 : options.set("priority").doc() =
53 : "Priorities of models in decreasing order. A model with higher priority will be evaluated "
54 2 : "first. This is useful for breaking cyclic dependency.";
55 :
56 4 : options.set<bool>("automatic_nonlinear_parameter") = true;
57 4 : options.set("automatic_nonlinear_parameter").doc() =
58 2 : "Whether to automatically add dependent nonlinear parameters";
59 :
60 2 : return options;
61 0 : }
62 :
63 77 : ComposedModel::ComposedModel(const OptionSet & options)
64 : : Model(options),
65 154 : _additional_outputs(options.get<std::vector<VariableName>>("additional_outputs")),
66 231 : _auto_nl_param(options.get<bool>("automatic_nonlinear_parameter"))
67 : {
68 : // Each sub-model shall have _independent_ output storage. This is because the same model could
69 : // be registered as a sub-model by different models, and it could be evaluated with _different_
70 : // input, and hence yields _different_ output values.
71 338 : for (const auto & model_name : options.get<std::vector<std::string>>("models"))
72 261 : register_model(model_name, /*nonlinear=*/false, /*merge_input=*/false);
73 :
74 : // Each sub-model may have nonlinear parameters. In our design, nonlinear parameters _are_
75 : // models. Since we do not want to put the burden of "adding nonlinear parameters in the input
76 : // file through the option 'models'" on users, we should do more behind the scenes to register
77 : // them.
78 : //
79 : // Registering nonlinear parameters here ensures dependency resolution. And if a nonlinear
80 : // parameter is registered by multiple models (which is very possible), we won't have to
81 : // evaluate the nonlinar parameter over and over again!
82 77 : auto submodels = registered_models();
83 77 : if (_auto_nl_param)
84 261 : for (auto & submodel : submodels)
85 262 : for (auto && [pname, param] : submodel->named_nonlinear_parameters(/*recursive=*/true))
86 78 : if (std::find(_registered_models.begin(), _registered_models.end(), param.provider) ==
87 156 : _registered_models.end())
88 260 : _registered_models.push_back(param.provider);
89 :
90 : // Add registered models as nodes in the dependency resolver
91 337 : for (auto & submodel : registered_models())
92 260 : _dependency.add_node(submodel.get());
93 79 : for (const auto & var : _additional_outputs)
94 2 : _dependency.add_additional_outbound_item(var);
95 :
96 : // Define priority in the event of cyclic dependency
97 77 : auto priority_order = options.get<std::vector<std::string>>("priority");
98 77 : size_t priority = priority_order.empty() ? 0 : priority_order.size() - 1;
99 77 : for (const auto & model_name : priority_order)
100 0 : _dependency.set_priority(registered_model(model_name).get(), priority--);
101 :
102 : // Resolve the dependency
103 77 : _dependency.unique_item_provider() = true;
104 77 : _dependency.unique_item_consumer() = false;
105 77 : _dependency.resolve();
106 77 : const auto & resolution = _dependency.resolution();
107 :
108 : // Sort the registered models by dependency resolution
109 77 : std::vector<std::shared_ptr<Model>> sorted_models(resolution.size());
110 337 : for (std::size_t i = 0; i < resolution.size(); ++i)
111 260 : sorted_models[i] = registered_model(resolution[i]->name());
112 77 : _registered_models = std::move(sorted_models);
113 :
114 : // Register input variables
115 475 : for (const auto & item : _dependency.inbound_items())
116 398 : if (!input_axis().has_variable(item.value))
117 321 : clone_input_variable(item.parent->input_variable(item.value));
118 :
119 : // Register output variables
120 180 : for (const auto & item : _dependency.outbound_items())
121 103 : clone_output_variable(item.parent->output_variable(item.value));
122 :
123 : // Declare nonlinear parameters
124 261 : for (auto & submodel : submodels)
125 262 : for (auto && [pname, param] : submodel->named_nonlinear_parameters(/*recursive=*/true))
126 78 : if (input_axis().has_variable(param.provider_var))
127 184 : register_nonlinear_parameter(pname, param);
128 :
129 : // Check if this composed model defines values
130 77 : _defines_value = true;
131 337 : for (const auto & submodel : registered_models())
132 260 : if (!submodel->defines_values())
133 : {
134 0 : _defines_value = false;
135 0 : _defines_dvalue = false;
136 0 : _defines_d2value = false;
137 0 : break;
138 : }
139 :
140 : // Check if this composed model defines derivatives
141 77 : if (_defines_value)
142 : {
143 77 : _defines_dvalue = true;
144 337 : for (const auto & submodel : registered_models())
145 260 : if (!submodel->defines_derivatives())
146 : {
147 0 : _defines_dvalue = false;
148 0 : _defines_d2value = false;
149 0 : break;
150 : }
151 : }
152 :
153 : // Check if this composed model defines second derivatives
154 77 : if (_defines_dvalue)
155 : {
156 77 : _defines_d2value = true;
157 193 : for (const auto & submodel : registered_models())
158 168 : if (!submodel->defines_second_derivatives())
159 : {
160 52 : _defines_d2value = false;
161 52 : break;
162 : }
163 : }
164 :
165 : // Is JIT enabled?
166 77 : _jit = Model::is_jit_enabled(); // boolean read from input file
167 :
168 : // If any submodel does not support JIT, disable JIT
169 77 : if (_jit)
170 321 : for (const auto & submodel : registered_models())
171 250 : if (!submodel->is_jit_enabled())
172 : {
173 4 : _jit = false;
174 4 : break;
175 : }
176 77 : }
177 :
178 : std::map<std::string, NonlinearParameter>
179 32 : ComposedModel::named_nonlinear_parameters(bool /*recursive*/) const
180 : {
181 32 : return _nl_params;
182 : }
183 :
184 : void
185 260 : ComposedModel::link_input_variables(Model * submodel)
186 : {
187 2311 : for (const auto & item : _dependency.inbound_items())
188 2051 : if (item.parent == submodel)
189 398 : submodel->input_variable(item.value).ref(input_variable(item.value));
190 :
191 1698 : for (const auto & [item, providers] : _dependency.item_providers())
192 1438 : if (item.parent == submodel)
193 : {
194 197 : auto * depmodel = providers.begin()->parent;
195 197 : submodel->input_variable(item.value).ref(depmodel->output_variable(item.value));
196 : }
197 260 : }
198 :
199 : void
200 260 : ComposedModel::link_output_variables(Model * submodel)
201 : {
202 673 : for (const auto & item : _dependency.outbound_items())
203 413 : if (item.parent == submodel)
204 103 : output_variable(item.value).ref(submodel->output_variable(item.value));
205 260 : }
206 :
207 : void
208 199 : ComposedModel::set_value(bool out, bool dout_din, bool d2out_din2)
209 : {
210 844 : for (const auto & i : registered_models())
211 : {
212 645 : if (out && !dout_din && !d2out_din2)
213 364 : i->forward_maybe_jit(true, false, false);
214 281 : else if (dout_din && !d2out_din2)
215 233 : i->forward_maybe_jit(true, true, false);
216 48 : else if (d2out_din2)
217 48 : i->forward_maybe_jit(true, true, true);
218 : else
219 0 : throw NEMLException("Unsupported call signature to set_value");
220 : }
221 :
222 199 : if (dout_din)
223 155 : for (auto && [name, var] : output_variables())
224 85 : var->apply_chain_rule(_dependency);
225 :
226 199 : if (d2out_din2)
227 32 : for (auto && [name, var] : output_variables())
228 16 : var->apply_second_order_chain_rule(_dependency);
229 199 : }
230 : } // namespace neml2
|