diff --git a/framework/include/userobjects/SolutionUserObjectBase.h b/framework/include/userobjects/SolutionUserObjectBase.h index 68bab49618f8..42eba191cc1c 100644 --- a/framework/include/userobjects/SolutionUserObjectBase.h +++ b/framework/include/userobjects/SolutionUserObjectBase.h @@ -512,6 +512,17 @@ class SolutionUserObjectBase : public GeneralUserObject /// Map from block names to block IDs. Read from the ExodusII file std::map _block_id_to_name; + // All the caches are duplicated between the before-timestep and after-timestep mesh functions + /// Cached points + mutable Point _cached_p = Point(std::numeric_limits::max(), 0., 0.); + mutable Point _cached_p2 = Point(std::numeric_limits::max(), 0., 0.); + /// Cached subdomain ids + mutable std::set _cached_subdomain_ids; + mutable std::set _cached_subdomain_ids2; + /// Cached values + mutable DenseVector _cached_values; + mutable DenseVector _cached_values2; + private: static Threads::spin_mutex _solution_user_object_mutex; }; diff --git a/framework/src/userobjects/SolutionUserObjectBase.C b/framework/src/userobjects/SolutionUserObjectBase.C index facda5465b23..ebb6cb70f032 100644 --- a/framework/src/userobjects/SolutionUserObjectBase.C +++ b/framework/src/userobjects/SolutionUserObjectBase.C @@ -292,16 +292,15 @@ SolutionUserObjectBase::readExodusII() { if (std::find(all_nodal.begin(), all_nodal.end(), var_name) != all_nodal.end()) _nodal_variables.push_back(var_name); - if (std::find(all_elemental.begin(), all_elemental.end(), var_name) != all_elemental.end()) + else if (std::find(all_elemental.begin(), all_elemental.end(), var_name) != + all_elemental.end()) _elemental_variables.push_back(var_name); - if (std::find(all_scalar.begin(), all_scalar.end(), var_name) != all_scalar.end()) - // Check if the scalar matches any field variables, and ignore the var if it does. This - // means its a Postprocessor. - if (std::find(begin(_nodal_variables), end(_nodal_variables), var_name) == - _nodal_variables.end() && - std::find(begin(_elemental_variables), end(_elemental_variables), var_name) == - _elemental_variables.end()) - _scalar_variables.push_back(var_name); + else if (std::find(all_scalar.begin(), all_scalar.end(), var_name) != all_scalar.end()) + // We checked other variables types first, so if a postprocessor has the same name as a + // field variable, it won't get loaded as a scalar variable + _scalar_variables.push_back(var_name); + else + paramError("system_variables", "Variable '" + var_name + "' was not found in Exodus file"); } } else @@ -463,6 +462,10 @@ SolutionUserObjectBase::timestepSetup() // Update time interpolation for ExodusII solution if (_file_type == 1 && _interpolate_times) updateExodusTimeInterpolation(); + + // Clear the caches + _cached_p(0) = std::numeric_limits::max(); + _cached_p2(0) = std::numeric_limits::max(); } void @@ -572,6 +575,10 @@ SolutionUserObjectBase::initialSetup() _local_variable_index[name] = i; } + // If the start time is not the same as in the exodus file, we may need this on INITIAL + if (_file_type == 1 && _interpolate_times) + updateExodusTimeInterpolation(); + // Set initialization flag _initialized = true; } @@ -1061,13 +1068,37 @@ SolutionUserObjectBase::evalMeshFunction(const Point & p, // Extract a value from the _mesh_function { Threads::spin_mutex::scoped_lock lock(_solution_user_object_mutex); + if (func_num == 1) + { + // Check the cache + if (p == _cached_p && (!subdomain_ids || (*subdomain_ids == _cached_subdomain_ids))) + return _cached_values(local_var_index); + + // else get a new value (*_mesh_function)(p, 0.0, output, subdomain_ids); + // and cache it + _cached_p = p; + if (subdomain_ids) + _cached_subdomain_ids = *subdomain_ids; + _cached_values = output; + } // Extract a value from _mesh_function2 else if (func_num == 2) - (*_mesh_function2)(p, 0.0, output, subdomain_ids); + { + // Check the cache + if (p == _cached_p2 && (!subdomain_ids || (*subdomain_ids == _cached_subdomain_ids2))) + return _cached_values2(local_var_index); + // else get a new value + (*_mesh_function2)(p, 0.0, output, subdomain_ids); + // and cache it + _cached_p2 = p; + if (subdomain_ids) + _cached_subdomain_ids2 = *subdomain_ids; + _cached_values2 = output; + } else mooseError("The func_num must be 1 or 2"); } diff --git a/test/tests/userobjects/solution_user_object/gold/read_exodus_multiple_vars_out.e b/test/tests/userobjects/solution_user_object/gold/read_exodus_multiple_vars_out.e new file mode 100644 index 000000000000..a9d3c6e1c4b9 Binary files /dev/null and b/test/tests/userobjects/solution_user_object/gold/read_exodus_multiple_vars_out.e differ diff --git a/test/tests/userobjects/solution_user_object/read_exodus_multiple_vars.i b/test/tests/userobjects/solution_user_object/read_exodus_multiple_vars.i new file mode 100644 index 000000000000..5a7dec1aedbc --- /dev/null +++ b/test/tests/userobjects/solution_user_object/read_exodus_multiple_vars.i @@ -0,0 +1,72 @@ +[Mesh] + [gen] + type = GeneratedMeshGenerator + dim = 2 + xmin = 0 + xmax = 1 + ymin = 0 + ymax = 1 + nx = 4 + ny = 2 + [] +[] + +[Problem] + kernel_coverage_check = false + skip_nl_system_check = true + solve = false +[] + +[AuxVariables] + [var1] + family = MONOMIAL + order = CONSTANT + [] + [var2] + family = MONOMIAL + order = CONSTANT + [] +[] + +# Auxkernels execute inside an element loop, the solution UO value +# should be cached when called from the first auxkernel, then retrieved by the second one +[AuxKernels] + [var1] + type = SolutionAux + variable = var1 + solution = soln + from_variable = var1 + execute_on = 'INITIAL' + [] + [var2] + type = SolutionAux + variable = var2 + solution = soln + from_variable = var2 + execute_on = 'INITIAL' + [] +[] + +# We use the same exodus solution file as the other test +[UserObjects] + [soln] + type = SolutionUserObject + mesh = write_exodus_initial_out.e + system_variables = 'var1 var2' + execute_on = 'INITIAL' + # timestep = 1 + [] +[] + +[Executioner] + type = Transient + num_steps = 2 + # Different dt to use interpolation + dt = 0.1 + start_time = 0.005 +[] + +[Outputs] + exodus = true + execute_on = FINAL +[] diff --git a/test/tests/userobjects/solution_user_object/tests b/test/tests/userobjects/solution_user_object/tests index 0ea71b9b6fdd..b719e98ca5ef 100644 --- a/test/tests/userobjects/solution_user_object/tests +++ b/test/tests/userobjects/solution_user_object/tests @@ -2,21 +2,21 @@ issues = '#7244' design = 'SolutionUserObject.md' - [./discontinuous_value_solution_uo_p1] + [discontinuous_value_solution_uo_p1] type = 'Exodiff' input = 'discontinuous_value_solution_uo_p1.i' exodiff = 'discontinuous_value_solution_uo_p1.e' requirement = 'The system shall be capable of writing out a solution file with both continuous and discontinuous fields for the purpose of verifying that ability to read those solutions back into a new simulation.' - [../] - [./discontinuous_value_solution_uo_p2] + [] + [discontinuous_value_solution_uo_p2] type = 'CSVDiff' input = 'discontinuous_value_solution_uo_p2.i' csvdiff = 'discontinuous_value_solution_uo_p2.csv' prereq = discontinuous_value_solution_uo_p1 requirement = 'The system shall be capable of reading in field information and producing gradient values from both continuous and discontinuous fields.' - [../] + [] [test_scalar] type = RunApp cli_args = 'UserObjects/soln/mesh=gold/testscalarrename.e UserObjects/soln/system_variables=""' @@ -54,4 +54,25 @@ issues = "#31238" requirement = "The system shall be able to read in a variable from a previous exodus file that only has fiels at time 0." [] + + [write_exodus_multiple_vars] + type = RunApp + prereq = 'read_exodus_initial' + input = "write_exodus_initial.i" + # Create more than 1 timestep and more than 1 variable + cli_args = "AuxVariables/var1/initial_condition=2 AuxVariables/var2/initial_condition=1 + AuxKernels/v1/type=FunctionAux AuxKernels/v1/variable=var1 AuxKernels/v1/function='2+t' + AuxKernels/v2/type=FunctionAux AuxKernels/v2/variable=var2 AuxKernels/v2/function='1+t' + Executioner/type=Transient Executioner/num_steps=10" + issues = "#31238" + requirement = "The system shall be capable of writing out an exodus file with multiple variables." + [] + [read_exodus_multiple_vars] + type = Exodiff + input = "read_exodus_multiple_vars.i" + exodiff = "read_exodus_multiple_vars_out.e" + prereq = "write_exodus_multiple_vars" + issues = "#31238 #31747 #31767" + requirement = "The system shall be able to read in multiple variables from a previous exodus file, loaded separately from the mesh file, leveraging caching and time step interpolation." + [] []