36#include <polysolve/linear/Solver.hpp>
37#include <polysolve/nonlinear/Solver.hpp>
49 json first_material(
const json &materials)
51 return materials.is_array() ? materials.front() : materials;
54 void disable_newton_psd_projection(
json &solver_params)
56 const auto disable_for_newton = [](
json ¶ms) {
57 if (!params.contains(
"Newton") || params[
"Newton"].is_null())
58 params[
"Newton"] = json::object();
59 params[
"Newton"][
"use_psd_projection"] =
false;
62 if (solver_params.contains(
"solver") && solver_params[
"solver"].is_array())
64 for (
json &strategy : solver_params[
"solver"])
66 const std::string type = strategy.value(
"type",
"");
67 if (type ==
"Newton" || type ==
"SparseNewton" || type ==
"sparse_newton"
68 || type ==
"DenseNewton" || type ==
"dense_newton")
69 disable_for_newton(strategy);
74 disable_for_newton(solver_params);
78 void assert_same_space_ids(
79 const json &materials,
80 const int displacement_space_id,
81 const int temperature_space_id)
85 if (material.at(
"displacement_space_id").get<
int>() != displacement_space_id
86 || material.at(
"temperature_space_id").get<
int>() != temperature_space_id)
93 json elastic_material_from_thermo_material(
const json &material)
95 if (!material.contains(
"elastic_material") || !material[
"elastic_material"].is_object())
96 log_and_throw_error(
"ThermoElasticity requires elastic_material to be an elastic material object.");
98 json elastic_material = material[
"elastic_material"];
99 const std::string type = elastic_material.value(
"type",
"");
101 log_and_throw_error(
"ThermoElasticity elastic_material must be an elastic material, got '{}'.", type);
103 if (material.contains(
"id"))
104 elastic_material[
"id"] = material[
"id"];
105 if (material.contains(
"rho"))
106 elastic_material[
"rho"] = material[
"rho"];
108 return elastic_material;
111 json solver_params_for_residual_mode(
const json &solver_params,
const bool is_residual)
113 json params = solver_params;
115 disable_newton_psd_projection(params);
119 std::string elastic_formulation_from_thermo_materials(
const json &materials)
121 std::string formulation;
124 const json elastic_material = elastic_material_from_thermo_material(material);
125 const std::string type = elastic_material[
"type"];
126 if (formulation.empty())
128 else if (formulation != type)
129 formulation =
"MultiModels";
137 std::vector<Eigen::Triplet<double>>
entries;
138 entries.reserve(a.nonZeros() +
b.nonZeros());
140 for (
int k = 0; k < a.outerSize(); ++k)
141 for (StiffnessMatrix::InnerIterator it(a, k); it; ++it)
142 entries.emplace_back(it.row(), it.col(), it.value());
144 for (
int k = 0; k <
b.outerSize(); ++k)
145 for (StiffnessMatrix::InnerIterator it(b, k); it; ++it)
146 entries.emplace_back(a.rows() + it.row(), a.cols() + it.col(), it.value());
150 out.makeCompressed();
192 const std::string &formulation,
195 const std::string &out_path)
200 const bool is_time_dependent =
args.contains(
"time") && !
args[
"time"].is_null();
203 if (
args[
"solver"][
"advanced"][
"check_inversion"] ==
"Conservative")
205 if (
auto elastic_assembler = std::dynamic_pointer_cast<assembler::ElasticityAssembler>(
primary_assembler_))
206 elastic_assembler->set_use_robust_jacobian();
216 problem = std::make_shared<assembler::GenericTensorProblem>(
"ThermoElasticDisplacement");
218 temperature_problem_ = std::make_shared<assembler::GenericScalarProblem>(
"ThermoElasticTemperature");
222 tmp[
"is_time_dependent"] = is_time_dependent;
226 auto bc =
args[
"boundary_conditions"];
238 t0 = is_time_dependent ?
args[
"time"][
"t0"].get<
double>() : 0.0;
239 time_steps = is_time_dependent ?
args[
"time"][
"time_steps"].get<
int>() : 0;
240 dt = is_time_dependent ?
args[
"time"][
"dt"].get<
double>() : 0.0;
242 this->args[
"contact"].erase(
"_dhat_was_explicit");
247 const json material = first_material(
args.at(
"materials"));
251 log_and_throw_error(
"ThermoElasticity requires distinct displacement and temperature FE spaces.");
259 if (
args[
"materials"].is_array())
261 json materials = json::array();
262 for (
const json &material :
args[
"materials"])
263 materials.push_back(elastic_material_from_thermo_material(material));
267 return elastic_material_from_thermo_material(
args[
"materials"]);
272 const json &integrators =
args[
"time"][
"integrator"];
273 if (!integrators.is_array())
276 for (
const json &integrator : integrators)
278 if (integrator.value(
"fe_space", -1) == fe_space_id)
280 json copy = integrator;
281 copy.erase(
"fe_space");
315 logger().info(
"Loading obstacles...");
331 Eigen::VectorXi displacement_orders;
334 if (
args[
"space"][
"use_p_ref"])
338 args[
"space"][
"advanced"][
"B"],
339 args[
"space"][
"advanced"][
"h1_formula"],
340 args[
"space"][
"discr_order"],
341 args[
"space"][
"advanced"][
"discr_order_max"],
343 displacement_orders);
345 logger().info(
"min p: {} max p: {}", displacement_orders.minCoeff(), displacement_orders.maxCoeff());
352 args[
"space"][
"basis_type"],
353 args[
"space"][
"poly_basis_type"],
356 args[
"space"][
"advanced"][
"quadrature_order"],
357 args[
"space"][
"advanced"][
"mass_quadrature_order"],
358 args[
"space"][
"advanced"][
"use_corner_quadrature"],
359 args[
"space"][
"advanced"][
"n_harmonic_samples"],
360 args[
"space"][
"advanced"][
"integral_constraints"],
371 logger().info(
"Building collision mesh...");
378 for (
int d = 0; d < mesh.
dimension(); ++d)
387 if (
args[
"space"][
"advanced"][
"count_flipped_els"])
390 const int n_samples = 10;
399 logger().info(
"Building cache...");
406 logger().info(
" took {}s", timer.getElapsedTime());
432 std::vector<int> unused_neumann_boundary_nodes;
440 unused_neumann_boundary_nodes,
459 for (
int d = 0; d < mesh.
dimension(); ++d)
471 Eigen::VectorXi temperature_orders;
478 args[
"space"][
"basis_type"],
479 args[
"space"][
"poly_basis_type"],
482 args[
"space"][
"advanced"][
"quadrature_order"],
483 args[
"space"][
"advanced"][
"mass_quadrature_order"],
484 args[
"space"][
"advanced"][
"use_corner_quadrature"],
485 args[
"space"][
"advanced"][
"n_harmonic_samples"],
486 args[
"space"][
"advanced"][
"integral_constraints"],
509 std::vector<int> unused_neumann_boundary_nodes;
517 unused_neumann_boundary_nodes,
543 json rhs_solver_params =
args[
"solver"][
"linear"];
544 if (!rhs_solver_params.contains(
"Pardiso"))
545 rhs_solver_params[
"Pardiso"] = {};
546 rhs_solver_params[
"Pardiso"][
"mtype"] = -2;
554 args[
"space"][
"advanced"][
"bc_method"],
566 args[
"space"][
"advanced"][
"bc_method"],
580 delta = (max - min) / 2. + min;
582 p_params[
"bbox_center"] = {delta(0), delta(1), delta(2)};
584 p_params[
"bbox_center"] = {delta(0), delta(1)};
593 logger().info(
"Assigning rhs...");
617 logger().info(
"Assembling mass mat...");
624 assert(
mass_.size() > 0);
626 for (
int k = 0; k <
mass_.outerSize(); ++k)
627 for (StiffnessMatrix::InnerIterator it(
mass_, k); it; ++it)
632 if (
args[
"solver"][
"advanced"][
"lump_mass_matrix"])
661 if (!was_solution_loaded)
666 const Eigen::MatrixXd &displacement,
667 const Eigen::MatrixXd &temperature)
const
671 assert(displacement.cols() == temperature.cols());
673 Eigen::MatrixXd solution(displacement.rows() + temperature.rows(), displacement.cols());
674 solution << displacement, temperature;
679 const Eigen::MatrixXd &solution,
680 Eigen::MatrixXd &displacement,
681 Eigen::MatrixXd &temperature)
const
690 assert(solution.cols() == 1);
693 const bool is_time_dependent =
problem->is_time_dependent();
694 const double form_dt = is_time_dependent ?
dt : 0.0;
700 Eigen::MatrixXd displacement, temperature;
703 if (is_time_dependent)
709 Eigen::MatrixXd displacement_solution, displacement_velocity, displacement_acceleration;
711 displacement_solution.col(0) = displacement;
722 for (
const auto &form :
forms)
740 t, form_dt,
mesh_->is_volume());
743 const int gdiscr_order =
mesh_->orders().size() <= 0 ? 1 :
mesh_->orders().maxCoeff();
752 false, is_time_dependent);
756 if (is_time_dependent)
762 Eigen::MatrixXd temperature_solution;
764 temperature_solution.col(0) = temperature;
765 Eigen::MatrixXd temperature_velocity = Eigen::MatrixXd::Zero(temperature_solution.rows(), temperature_solution.cols());
766 Eigen::MatrixXd temperature_acceleration = Eigen::MatrixXd::Zero(temperature_solution.rows(), temperature_solution.cols());
773 [
this, temperature_boundary_samples](
775 const Eigen::VectorXd &,
776 Eigen::VectorXd &target) {
777 Eigen::MatrixXd projected_target = target;
778 const std::vector<mesh::LocalBoundary> empty_neumann_boundary;
782 temperature_boundary_samples,
783 empty_neumann_boundary,
787 assert(projected_target.cols() == 1);
788 target = projected_target.col(0);
803 for (
const auto &form :
forms)
809 auto stacked_al = std::make_shared<solver::StackedAugmentedLagrangianForm>();
810 const auto displacement_al_block = stacked_al->add_block(displacement_block.size());
811 const auto temperature_al_block = stacked_al->add_block(temperature_block.size());
816 displacement_al_block,
817 std::make_shared<solver::BCLagrangianForm>(
818 displacement_block.size(),
827 temperature_al_block,
828 std::make_shared<solver::BCLagrangianForm>(
829 temperature_block.size(),
833 0, is_time_dependent, t));
842 assert(
problem->is_time_dependent());
864 const json nonlinear_params = solver_params_for_residual_mode(
args[
"solver"][
"nonlinear"], nl_problem.
is_residual());
865 const json al_nonlinear_params = solver_params_for_residual_mode(
args[
"solver"][
"augmented_lagrangian"][
"nonlinear"], nl_problem.
is_residual());
867 std::shared_ptr<polysolve::nonlinear::Solver> nl_solver =
868 polysolve::nonlinear::Solver::create(
869 nonlinear_params,
args[
"solver"][
"linear"],
875 const auto update_displacement_barrier_stiffness = [&](
const Eigen::VectorXd &
x) {
884 args[
"solver"][
"augmented_lagrangian"][
"initial_weight"],
885 args[
"solver"][
"augmented_lagrangian"][
"scaling"],
886 args[
"solver"][
"augmented_lagrangian"][
"max_weight"],
887 args[
"solver"][
"augmented_lagrangian"][
"eta"],
888 update_displacement_barrier_stiffness);
892 {{
"type", al_weight > 0 ?
"al" :
"rc"},
894 {
"info", nl_solver->info()}});
901 nl_problem, solution, al_nonlinear_params,
904 nl_problem, solution, nonlinear_params,
909 Eigen::VectorXd
x = solution;
911 update_displacement_barrier_stiffness(
x);
913 nl_solver->minimize(nl_problem,
x);
916 stats.
solver_info.push_back({{
"type",
"rc"}, {
"t", step}, {
"info", nl_solver->info()}});
926 logger().info(
"Solving ThermoElasticity");
933 Eigen::MatrixXd displacement, temperature;
936 const int cols = std::max(displacement.cols(), temperature.cols());
937 if (displacement.cols() != cols)
938 displacement.conservativeResize(Eigen::NoChange, cols);
939 if (temperature.cols() != cols)
940 temperature.conservativeResize(Eigen::NoChange, cols);
945 sol.conservativeResize(Eigen::NoChange, 1);
950 double characteristic_length = 0;
951 if (
args[
"solver"][
"advanced"][
"characteristic_length"] > 0)
952 characteristic_length =
args[
"solver"][
"advanced"][
"characteristic_length"];
956 mesh_->bounding_box(min, max);
957 characteristic_length = (max - min).norm();
960 double characteristic_force_density = 0;
961 if (
args[
"solver"][
"advanced"][
"characteristic_force_density"] <= 0)
963 logger().warn(
"No user-specified force density was provided, defaulting to 10000.");
964 characteristic_force_density = 10000;
967 characteristic_force_density =
args[
"solver"][
"advanced"][
"characteristic_force_density"];
972 polysolve::linear::Solver::create(
args[
"solver"][
"linear"],
logger()),
973 characteristic_length, characteristic_force_density,
981 if (!
problem->is_time_dependent())
990 const double time =
t0 +
dt * t;
995 Eigen::MatrixXd displacement, temperature;
1016 if (!
args[
"output"][
"advanced"][
"compute_error"])
1019 Eigen::MatrixXd displacement, temperature;
1023 if (!
args[
"time"].is_null())
1024 tend =
args[
"time"][
"tend"];
1032 const Eigen::MatrixXd &solution,
1035 Eigen::MatrixXd displacement, temperature;
1038 std::vector<io::OutputField> fields =
1042 fields.begin(), fields.end(),
1044 return field.name ==
"solution" || field.name ==
"solution_gradient";
1048 if (!
mesh_ || temperature.size() <= 0)
1056 const auto has_field = [&](
const std::string &
name) {
1057 return std::any_of(fields.begin(), fields.end(), [&](
const io::OutputField &field) {
1058 return field.name == name;
1063 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
1064 if (!paraview_options[
"material"] || !has_element_samples)
1067 const auto params = assembler.parameters();
1068 std::map<std::string, Eigen::MatrixXd> param_values;
1069 for (
const auto &[p, _] : params)
1070 param_values[p].setZero(output_rows, 1);
1078 for (
const auto &[p, func] : params)
1082 for (
const auto &[
name, values] : param_values)
1087 const auto sample_temperature = [&](Eigen::MatrixXd &values, Eigen::MatrixXd *gradients =
nullptr) ->
bool {
1088 if (has_element_samples)
1090 values.resize(sample.local_points.rows(), 1);
1092 gradients->resize(sample.local_points.rows(), mesh_->dimension());
1093 for (
int i = 0; i < sample.local_points.rows(); ++i)
1095 const int element_id = sample.element_ids(i);
1100 gradients->row(i).setZero();
1104 Eigen::MatrixXd local_sol, local_grad;
1106 *mesh_, 1, temperature_space_.basis_list(), temperature_space_.geometry_basis_list(),
1107 element_id, sample.local_points.row(i), temperature, local_sol, local_grad);
1108 values(i) = local_sol(0);
1110 gradients->row(i) = local_grad;
1113 if (output_rows > values.rows())
1115 const int previous_rows = values.rows();
1116 values.conservativeResize(output_rows, Eigen::NoChange);
1117 values.bottomRows(output_rows - previous_rows).setZero();
1120 gradients->conservativeResize(output_rows, Eigen::NoChange);
1121 gradients->bottomRows(output_rows - previous_rows).setZero();
1127 if (sample.node_ids.size() > 0)
1129 values.resize(sample.node_ids.size(), 1);
1130 for (
int i = 0; i < sample.node_ids.size(); ++i)
1132 const int node_id = sample.node_ids(i);
1133 if (node_id < 0 || node_id >= temperature.rows())
1135 values(i) = temperature(node_id);
1137 return sample.points.rows() == 0 || sample.points.rows() == values.rows();
1143 const bool export_temperature_gradient =
1144 !options.fields.empty() && options.export_field(
"temperature_gradient");
1145 if (options.export_field(
"temperature") || export_temperature_gradient)
1147 Eigen::MatrixXd values, gradients;
1148 if (sample_temperature(values, export_temperature_gradient ? &gradients : nullptr))
1150 if (options.export_field(
"temperature"))
1152 if (export_temperature_gradient)
1157 if (thermoelastic_assembler_)
1158 append_material_fields(*thermoelastic_assembler_);
1159 if (temperature_assembler_)
1160 append_material_fields(*temperature_assembler_);
1161 if (temperature_mass_assembler_)
1162 append_material_fields(*temperature_mass_assembler_);
std::vector< Eigen::Triplet< double > > entries
#define POLYFEM_SCOPED_TIMER(...)
double characteristic_length() const
static std::shared_ptr< MixedNLAssembler > make_mixed_nl_assembler(const std::string &formulation)
static bool is_elastic_material(const std::string &material)
utility to check if material is one of the elastic materials
static std::shared_ptr< Assembler > make_assembler(const std::string &formulation)
void init(const bool is_volume, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const bool is_mass=false)
computes the basis evaluation and geometric mapping for each of the given ElementBases in bases initi...
void init_empty(const bool is_mass=false)
initialize an empty cache.
static void interpolate_at_local_vals(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const int el_index, const Eigen::MatrixXd &local_pts, const Eigen::MatrixXd &fun, Eigen::MatrixXd &result, Eigen::MatrixXd &result_grad)
interpolate solution and gradient at element (calls interpolate_at_local_vals with sol)
double assigning_rhs_time
time to computing the rhs
double assembling_mass_mat_time
time to assembly mass
double solving_time
time to solve
int n_flipped
number of flipped elements, compute only when using count_flipped_els (false by default)
json solver_info
information of the solver, eg num iteration, time, errors, etc the informations varies depending on t...
Eigen::Vector4d spectrum
spectrum of the stiffness matrix, enable only if POLYSOLVE_WITH_SPECTRA is ON (off by default)
void count_flipped_elements(const polyfem::mesh::Mesh &mesh, const std::vector< polyfem::basis::ElementBases > &gbases)
counts the number of flipped elements
void compute_errors(const int n_bases, const std::vector< polyfem::basis::ElementBases > &bases, const std::vector< polyfem::basis::ElementBases > &gbases, const polyfem::mesh::Mesh &mesh, const assembler::Problem &problem, const double tend, const Eigen::MatrixXd &sol)
compute errors
void compute_mesh_size(const polyfem::mesh::Mesh &mesh_in, const std::vector< polyfem::basis::ElementBases > &bases_in, const int n_samples, const bool use_curved_mesh_size)
computes the mesh size, it samples every edges n_samples times uses curved_mesh_size (false by defaul...
long long nn_zero
non zeros and sytem matrix size num dof is the total dof in the system
double mesh_size
max edge lenght
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
virtual int get_body_id(const int primitive) const
Get the volume selection of an element (cell in 3d, face in 2d)
virtual void bounding_box(RowVectorNd &min, RowVectorNd &max) const =0
computes the bbox of the mesh
virtual bool is_volume() const =0
checks if mesh is volume
void update_nodes(const Eigen::VectorXi &in_node_to_node)
Update the node ids to reorder them.
int dimension() const
utily for dimension
virtual int get_node_id(const int node_id) const
Get the boundary selection of a node.
static void p_refine(const mesh::Mesh &mesh, const double B, const bool h1_formula, const int base_p, const int discr_order_max, io::OutStatsData &stats, Eigen::VectorXi &disc_orders)
compute a priori prefinement
void solve_reduced(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
std::function< void(const double)> post_subsolve
void solve_al(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
bool uses_lagging() const
bool is_residual() const override
virtual void init(const TVector &x0) override
double normalize_forms() override
void init_lagging(const TVector &x) override
void update_dt()
updates the dt inside the different forms
std::shared_ptr< solver::NLProblem > nl_problem
std::vector< std::shared_ptr< solver::AugmentedLagrangianForm > > al_form
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > time_integrator
void update_barrier_stiffness(const Eigen::VectorXd &x)
update the barrier stiffness for the forms
std::shared_ptr< assembler::RhsAssembler > rhs_assembler
static std::shared_ptr< ImplicitTimeIntegrator > construct_time_integrator(const json ¶ms, DynamicOrder dynamic_order=DynamicOrder::Second)
Factory method for constructing an implicit time integrator.
Obstacle read_obstacle_geometry(const Units &units, const json &geometry, const std::vector< json > &displacements, const std::vector< json > &dirichlets, const std::string &root_path, const int dim, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read a FEM mesh from a geometry JSON
Eigen::SparseMatrix< double > lump_matrix(const Eigen::SparseMatrix< double > &M)
Lump each row of a matrix into the diagonal.
Eigen::SparseMatrix< double > sparse_identity(int rows, int cols)
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
spdlog::logger & logger()
Retrieves the current logger.
std::array< int, 2 > QuadratureOrders
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
void log_and_throw_error(const std::string &msg)
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
bool export_field(const std::string &field) const
Eigen::VectorXi element_ids
Eigen::MatrixXd local_points