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-10-02 16:03:03 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              : #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
        

Generated by: LCOV version 2.0-1