LCOV - code coverage report
Current view: top level - models - ComposedModel.cxx (source / functions) Coverage Total Hit
Test: coverage.info Lines: 91.2 % 114 104
Test Date: 2025-06-29 01:25:44 Functions: 100.0 % 6 6

            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
        

Generated by: LCOV version 2.0-1