From 861269f4f8af3ee404eae7f5da1bb18ea3d96485 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 28 Jun 2022 19:06:51 -0600 Subject: [PATCH 01/72] add code meant to reduce allocations in current load --- src/physics/electromagnetics/current_load.cpp | 95 +++++++++++-------- src/physics/electromagnetics/current_load.hpp | 6 ++ src/utils/irrotational_projector.cpp | 53 ++++++----- src/utils/irrotational_projector.hpp | 3 +- src/utils/mesh_warper/mesh_warper.cpp | 37 +++++--- test/regression/test_magnetostatic_box.cpp | 12 ++- test/regression/test_steady_vortex.cpp | 4 +- 7 files changed, 124 insertions(+), 86 deletions(-) diff --git a/src/physics/electromagnetics/current_load.cpp b/src/physics/electromagnetics/current_load.cpp index 6be33cbb..7152ed2d 100644 --- a/src/physics/electromagnetics/current_load.cpp +++ b/src/physics/electromagnetics/current_load.cpp @@ -7,6 +7,7 @@ #include "current_source_functions.hpp" #include "mach_input.hpp" #include "mfem_common_integ.hpp" +#include "mfem_extensions.hpp" #include "current_load.hpp" @@ -192,6 +193,13 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, scratch(&fes), // scratch(0), load(fes.GetTrueVSize()), + pcg(constructLinearSolver(fes.GetComm(), + {{"type", "pcg"}, + {"reltol", 1e-12}, + {"abstol", 1e-12}, + {"maxiter", 250}, + {"printlevel", 2}}, + &amg)), div_free_proj(h1_fes, fes, h1_fes.GetElementTransformation(0)->OrderW() + @@ -201,6 +209,14 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, m_l_mesh_sens(new VectorFEMassIntegratorMeshSens), dirty(true) { + amg.SetPrintLevel(0); + dynamic_cast(*pcg).SetPrintLevel( + IterativeSolver::PrintLevel().Warnings().Errors().Iterations()); + + /// project current coeff as initial guess for iterative solve + j.ProjectCoefficient(current); + div_free_current_vec = 0.0; + /// Create a H(curl) mass matrix for integrating grid functions nd_mass.AddDomainIntegrator(new VectorFEMassIntegrator); @@ -225,8 +241,6 @@ void CurrentLoad::assembleLoad() /// assemble linear form J.Assemble(); - /// project current coeff as initial guess for iterative solve - j.ProjectCoefficient(current); // mfem::ParaViewDataCollection pv("CurrentDensity", // j.ParFESpace()->GetParMesh()); // pv.SetPrefixPath("ParaView"); @@ -236,16 +250,16 @@ void CurrentLoad::assembleLoad() // pv.RegisterField("CurrentDensity", &j); // pv.Save(); - mfem::ParaViewDataCollection paraview_dc("current_density", - j.ParFESpace()->GetParMesh()); - paraview_dc.SetPrefixPath("ParaView"); - paraview_dc.SetLevelsOfDetail(2); - paraview_dc.SetDataFormat(VTKFormat::BINARY); - paraview_dc.SetHighOrderOutput(true); - paraview_dc.SetCycle(0); - paraview_dc.SetTime(0.0); - paraview_dc.RegisterField("current_density", &j); - paraview_dc.Save(); + // mfem::ParaViewDataCollection paraview_dc("current_density", + // j.ParFESpace()->GetParMesh()); + // paraview_dc.SetPrefixPath("ParaView"); + // paraview_dc.SetLevelsOfDetail(2); + // paraview_dc.SetDataFormat(VTKFormat::BINARY); + // paraview_dc.SetHighOrderOutput(true); + // paraview_dc.SetCycle(0); + // paraview_dc.SetTime(0.0); + // paraview_dc.RegisterField("current_density", &j); + // paraview_dc.Save(); /// assemble mass matrix delete nd_mass.LoseMat(); @@ -255,10 +269,10 @@ void CurrentLoad::assembleLoad() // HypreParMatrix M; OperatorHandle M(Operator::Hypre_ParCSR); - Vector X; - Vector RHS; - Array ess_tdof_list; - nd_mass.FormLinearSystem(ess_tdof_list, j, J, M, X, RHS); + // Vector X; + // Vector RHS; + // Array ess_tdof_list; + nd_mass.FormLinearSystem(dummmy_ess_tdof_list, j, J, M, X, RHS); // OperatorHandle M(Operator::Hypre_ParCSR); // nd_mass.ParallelAssemble(M); @@ -268,41 +282,42 @@ void CurrentLoad::assembleLoad() // Vector RHS(fes.GetTrueVSize()); // J.ParallelAssemble(RHS); - HypreBoomerAMG amg(*M.As()); - // HypreILU ilu; - // // HYPRE_ILUSetType(ilu, ); - // HYPRE_ILUSetLevelOfFill(ilu, 4); - // HYPRE_ILUSetLocalReordering(ilu, ); - // HYPRE_ILUSetPrintLevel(ilu, ); + // HypreBoomerAMG amg(*M.As()); + // // HypreILU ilu; + // // // HYPRE_ILUSetType(ilu, ); + // // HYPRE_ILUSetLevelOfFill(ilu, 4); + // // HYPRE_ILUSetLocalReordering(ilu, ); + // // HYPRE_ILUSetPrintLevel(ilu, ); - // HypreBoomerAMG amg(M); - amg.SetPrintLevel(-1); + // // HypreBoomerAMG amg(M); + // amg.SetPrintLevel(-1); // HyprePCG pcg(*M.As()); - HypreGMRES pcg(*M.As()); - // HyprePCG pcg(M); - // MINRESSolver pcg(fes.GetComm()); - pcg.SetTol(1e-12); - pcg.SetMaxIter(250); - pcg.SetPrintLevel(2); - pcg.SetPreconditioner(amg); - // pcg.SetPreconditioner(ilu); - pcg.SetKDim(250); - pcg.SetOperator(*M.As()); + // // HypreGMRES pcg(*M.As()); + // // HyprePCG pcg(M); + // // MINRESSolver pcg(fes.GetComm()); + // pcg.SetTol(1e-12); + // pcg.SetMaxIter(250); + // pcg.SetPrintLevel(2); + // pcg.SetPreconditioner(amg); + // // pcg.SetPreconditioner(ilu); + // // pcg.SetKDim(250); + // pcg.SetOperator(*M.As()); + pcg->SetOperator(*M); std::cout << "Inverting current load mass matrix:\n"; - pcg.Mult(RHS, X); + // pcg.Mult(RHS, X); + pcg->Mult(RHS, X); - Vector res(RHS.Size()); - M->Mult(X, res); - res -= RHS; - std::cout << "Residual Norm: " << res.Norml2() << "\n"; + // Vector res(RHS.Size()); + // M->Mult(X, res); + // res -= RHS; + // std::cout << "Residual Norm: " << res.Norml2() << "\n"; nd_mass.RecoverFEMSolution(X, J, j); // j.SetFromTrueDofs(X); /// Compute the discretely divergence-free portion of j - div_free_current_vec = 0.0; div_free_proj.Mult(j, div_free_current_vec); /** alternative approaches for computing dual */ diff --git a/src/physics/electromagnetics/current_load.hpp b/src/physics/electromagnetics/current_load.hpp index d3412d13..a320983b 100644 --- a/src/physics/electromagnetics/current_load.hpp +++ b/src/physics/electromagnetics/current_load.hpp @@ -61,6 +61,12 @@ class CurrentLoad final // mfem::Vector scratch; mfem::Vector load; + mfem::Vector X; + mfem::Vector RHS; + mfem::Array dummmy_ess_tdof_list; + mfem::HypreBoomerAMG amg; + std::unique_ptr pcg; + DivergenceFreeProjector div_free_proj; mfem::ParLinearForm mesh_sens; diff --git a/src/utils/irrotational_projector.cpp b/src/utils/irrotational_projector.cpp index 3852119a..0ef9e5cb 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -21,6 +21,15 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, div_x(&h1_fes), pcg(h1_fes.GetComm()) { + psi = 0.0; + amg.SetPrintLevel(0); + + pcg.SetRelTol(1e-14); + pcg.SetAbsTol(1e-14); + pcg.SetMaxIter(200); + pcg.SetPrintLevel(IterativeSolver::PrintLevel().Warnings().Errors()); + pcg.SetPreconditioner(amg); + /// not sure if theres a better way to handle this ess_bdr.SetSize(h1_fes.GetParMesh()->bdr_attributes.Max()); ess_bdr = 1; @@ -69,18 +78,10 @@ void IrrotationalProjector::Mult(const Vector &x, Vector &y) const div_x *= -1.0; // Apply essential BC and form linear system - psi = 0.0; HypreParMatrix D_mat; diffusion.FormLinearSystem(ess_bdr_tdofs, psi, div_x, D_mat, Psi, RHS); - amg.SetOperator(D_mat); - amg.SetPrintLevel(0); - pcg.SetOperator(D_mat); - pcg.SetTol(1e-14); - pcg.SetMaxIter(200); - pcg.SetPrintLevel(0); - pcg.SetPreconditioner(amg); // Solve the linear system for Psi pcg.Mult(RHS, Psi); @@ -117,14 +118,14 @@ void IrrotationalProjector::vectorJacobianProduct(const mfem::Vector &x, diffusion.FormLinearSystem( ess_bdr_tdofs, psi_bar, GTproj_bar, D_mat, Psi, RHS); auto D_matT = std::unique_ptr(D_mat.Transpose()); - amg.SetOperator(*D_matT); - amg.SetPrintLevel(0); + // amg.SetOperator(*D_matT); + // amg.SetPrintLevel(0); pcg.SetOperator(*D_matT); - pcg.SetTol(1e-14); - pcg.SetMaxIter(200); - pcg.SetPrintLevel(0); - pcg.SetPreconditioner(amg); + // pcg.SetTol(1e-14); + // pcg.SetMaxIter(200); + // pcg.SetPrintLevel(0); + // pcg.SetPreconditioner(amg); // Solve the linear system for Psi pcg.Mult(RHS, Psi); @@ -150,14 +151,14 @@ void IrrotationalProjector::vectorJacobianProduct(const mfem::Vector &x, HypreParMatrix D_mat; diffusion.FormLinearSystem(ess_bdr_tdofs, psi, div_x, D_mat, Psi, RHS); - amg.SetOperator(D_mat); - amg.SetPrintLevel(0); + // amg.SetOperator(D_mat); + // amg.SetPrintLevel(0); pcg.SetOperator(D_mat); - pcg.SetTol(1e-14); - pcg.SetMaxIter(200); - pcg.SetPrintLevel(0); - pcg.SetPreconditioner(amg); + // pcg.SetTol(1e-14); + // pcg.SetMaxIter(200); + // pcg.SetPrintLevel(0); + // pcg.SetPreconditioner(amg); // Solve the linear system for Psi pcg.Mult(RHS, Psi); @@ -176,14 +177,14 @@ void IrrotationalProjector::vectorJacobianProduct(const mfem::Vector &x, diffusion.FormLinearSystem( ess_bdr_tdofs, psi_bar, GTproj_bar, D_mat, Psi, RHS); auto D_matT = std::unique_ptr(D_mat.Transpose()); - amg.SetOperator(*D_matT); - amg.SetPrintLevel(0); + // amg.SetOperator(*D_matT); + // amg.SetPrintLevel(0); pcg.SetOperator(*D_matT); - pcg.SetTol(1e-14); - pcg.SetMaxIter(200); - pcg.SetPrintLevel(0); - pcg.SetPreconditioner(amg); + // pcg.SetTol(1e-14); + // pcg.SetMaxIter(200); + // pcg.SetPrintLevel(0); + // pcg.SetPreconditioner(amg); // Solve the linear system for Psi pcg.Mult(RHS, Psi); diff --git a/src/utils/irrotational_projector.hpp b/src/utils/irrotational_projector.hpp index 55517319..6d503859 100644 --- a/src/utils/irrotational_projector.hpp +++ b/src/utils/irrotational_projector.hpp @@ -98,7 +98,8 @@ class IrrotationalProjector : public mfem::Operator mutable mfem::Vector RHS; mutable mfem::HypreBoomerAMG amg; - mutable mfem::HyprePCG pcg; + // mutable mfem::HyprePCG pcg; + mutable mfem::CGSolver pcg; mfem::Array ess_bdr, ess_bdr_tdofs; }; diff --git a/src/utils/mesh_warper/mesh_warper.cpp b/src/utils/mesh_warper/mesh_warper.cpp index e9e17e5b..ff7b3ea5 100644 --- a/src/utils/mesh_warper/mesh_warper.cpp +++ b/src/utils/mesh_warper/mesh_warper.cpp @@ -250,40 +250,49 @@ MeshWarper::MeshWarper(MPI_Comm incomm, { auto num_states = mesh().SpaceDimension(); - FiniteElementState state(mesh(), options["space-dis"], num_states, "state"); - fields.emplace("state", std::move(state)); + fields.emplace( + "state", + FiniteElementState(mesh(), options["space-dis"], num_states, "state")); - FiniteElementState adjoint( - mesh(), options["space-dis"], num_states, "adjoint"); - fields.emplace("adjoint", std::move(adjoint)); + fields.emplace( + "adjoint", + FiniteElementState(mesh(), options["space-dis"], num_states, "adjoint")); - FiniteElementDual residual( - mesh(), options["space-dis"], num_states, "residual"); - duals.emplace("residual", std::move(residual)); + duals.emplace( + "residual", + FiniteElementDual(mesh(), options["space-dis"], num_states, "residual")); auto &mesh_gf = *dynamic_cast(mesh().GetNodes()); auto *mesh_fespace = mesh_gf.ParFESpace(); /// create new state vector copying the mesh's fe space - fields.emplace(std::piecewise_construct, - std::forward_as_tuple("mesh_coords"), - std::forward_as_tuple(mesh(), *mesh_fespace, "mesh_coords")); - FiniteElementState &mesh_coords = fields.at("mesh_coords"); + // fields.emplace(std::piecewise_construct, + // std::forward_as_tuple("mesh_coords"), + // std::forward_as_tuple(mesh(), *mesh_fespace, + // "mesh_coords")); + // FiniteElementState & = fields.at("mesh_coords"); + + FiniteElementState mesh_coords(mesh(), *mesh_fespace, "mesh_coords"); /// set the values of the new GF to those of the mesh's old nodes mesh_coords.gridFunc() = mesh_gf; - // mesh_coords.setTrueVec(); // distribute coords + /// tell the mesh to use this GF for its Nodes /// (and that it doesn't own it) mesh().NewNodes(mesh_coords.gridFunc(), false); /// Set initial volume coords true vec - fields.at("mesh_coords").setTrueVec(vol_coords); + mesh_coords.setTrueVec(vol_coords); + + /// Place mesh_coords into the solver's fields map + fields.emplace("mesh_coords", std::move(mesh_coords)); /// Get the indices of the surface mesh dofs into the volume mesh mfem::Array ess_bdr(mesh().bdr_attributes.Max()); ess_bdr = 1; fes().GetEssentialTrueDofs(ess_bdr, surface_indices); + std::cout << "Creating MeshWarper with " + << fes().GetTrueVSize() - surface_indices.Size() << " dofs!\n"; /// Set the initial surface coords vol_coords.GetSubVector(surface_indices, surf_coords); diff --git a/test/regression/test_magnetostatic_box.cpp b/test/regression/test_magnetostatic_box.cpp index 1da50cb0..a90f1f31 100644 --- a/test/regression/test_magnetostatic_box.cpp +++ b/test/regression/test_magnetostatic_box.cpp @@ -110,10 +110,10 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", /// number of elements in Z direction auto nz = 2; - for (int order = 1; order <= 4; ++order) + for (int order = 1; order <= 1; ++order) { options["space-dis"]["degree"] = order; - int nxy = 1; + int nxy = 4; for (int ref = 1; ref <= 1; ++ref) { nxy *= 2; @@ -133,14 +133,18 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", double error = solver.calcStateError(aexact, state_tv); std::cout.precision(10); std::cout << "error: " << error << "\n"; - REQUIRE(error == Approx(target_error[order-1][ref - 1]).margin(1e-10)); + // REQUIRE(error == Approx(target_error[order-1][ref - 1]).margin(1e-10)); /// Calculate the magnetic energy and check against target energy solver.createOutput("energy"); MachInputs inputs{{"state", state_tv}}; double energy = solver.calcOutput("energy", inputs); std::cout << "energy: " << energy << "\n"; - REQUIRE(energy == Approx(target_energy[order-1][ref - 1]).margin(1e-10)); + // REQUIRE(energy == Approx(target_energy[order-1][ref - 1]).margin(1e-10)); + + solver.solveForState({{"current_density:box", 2.0}}, state_tv); + solver.solveForState({{"current_density:box", 3.0}}, state_tv); + solver.solveForState({{"current_density:box", 4.0}}, state_tv); } } } diff --git a/test/regression/test_steady_vortex.cpp b/test/regression/test_steady_vortex.cpp index 33369cd5..09e7083a 100644 --- a/test/regression/test_steady_vortex.cpp +++ b/test/regression/test_steady_vortex.cpp @@ -4,7 +4,9 @@ #include "catch.hpp" -#include "mach.hpp" +// #include "mach.hpp" +#include "euler_fluxes.hpp" +#include "flow_solver.hpp" using namespace std; using namespace mfem; From 7bd6bb9a0af2cf34e149fb005c4b83c2985765f6 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Sat, 16 Jul 2022 15:48:35 -0600 Subject: [PATCH 02/72] switching linear solvers in current load, trying new BH curves --- src/common/material_library.cpp | 65 +++++++++++-------- src/physics/electromagnetics/current_load.cpp | 10 ++- src/utils/irrotational_projector.cpp | 3 +- src/utils/irrotational_projector.hpp | 3 +- test/unit/test_magnetic_load.cpp | 4 +- 5 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/common/material_library.cpp b/src/common/material_library.cpp index 6a65ed90..a91d7423 100644 --- a/src/common/material_library.cpp +++ b/src/common/material_library.cpp @@ -80,33 +80,46 @@ const nlohmann::json material_library{ {"alpha", 1.286}}}, {"hiperco50", {{"B", - {0.0, - 0.0, - 0.0, - 0.0, - 1.424590497285806, - 1.879802197738017, - 2.089177970766961, - 2.184851867536579, - 2.229448683, - 2.264761102805212, - 2.302883806114454, - 7.290145827, - 7.290145827, - 7.290145827, - 7.290145827}}, + // {0.0, + // 0.0, + // 0.0, + // 0.0, + // 1.424590497285806, + // 1.879802197738017, + // 2.089177970766961, + // 2.184851867536579, + // 2.229448683, + // 2.264761102805212, + // 2.302883806114454, + // 7.290145827, + // 7.290145827, + // 7.290145827, + // 7.290145827}}, + {0. , 0. , 0. , 0. , 0.2 , 0.3 , 0.4 , 0.5 , + 0.6 , 0.7 , 0.8 , 0.9 , 1. , 1.1 , 1.1976, 1.3 , + 1.4 , 1.4775, 1.6 , 1.7119, 1.8 , 1.9 , 2. , 2.1 , + 2.1868, 2.25 , 2.3 , 2.36 , 2.4 , 2.5 , 2.5 , 2.5 , + 2.5}}, {"H", - {0.0, - 8.993187962999999, - 10.978287105, - 83.247282676, - 211.230768762, - 458.336047457, - 1159.131573849, - 2773.494054824, - 1.342303920594017e6, - 2.675328620250853e6, - 3.9982409587815357e6}}, + // {0.0, + // 8.993187962999999, + // 10.978287105, + // 83.247282676, + // 211.230768762, + // 458.336047457, + // 1159.131573849, + // 2773.494054824, + // 1.342303920594017e6, + // 2.675328620250853e6, + // 3.9982409587815357e6}}, + {-8.45567148e-16, 1.52187154e+01, 2.44069859e+01, 2.87543851e+01, + 3.28574240e+01, 3.53931188e+01, 3.83409008e+01, 4.10642780e+01, + 4.38435871e+01, 4.64801737e+01, 4.96317182e+01, 5.29859037e+01, + 5.66857479e+01, 6.15933673e+01, 6.64821406e+01, 7.75969027e+01, + 9.75330122e+01, 1.21434401e+02, 1.52150611e+02, 1.94912815e+02, + 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, + 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, + 3.97887360e+04}}, {"mu_r", 750}, {"rho", 8110.0}, {"cv", 0.420}, diff --git a/src/physics/electromagnetics/current_load.cpp b/src/physics/electromagnetics/current_load.cpp index 7152ed2d..d2be15db 100644 --- a/src/physics/electromagnetics/current_load.cpp +++ b/src/physics/electromagnetics/current_load.cpp @@ -194,10 +194,12 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, // scratch(0), load(fes.GetTrueVSize()), pcg(constructLinearSolver(fes.GetComm(), - {{"type", "pcg"}, + {{"type", "gmres"}, + {"kdim", 500}, + // {{"type", "pcg"}, {"reltol", 1e-12}, {"abstol", 1e-12}, - {"maxiter", 250}, + {"maxiter", 500}, {"printlevel", 2}}, &amg)), div_free_proj(h1_fes, @@ -209,7 +211,8 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, m_l_mesh_sens(new VectorFEMassIntegratorMeshSens), dirty(true) { - amg.SetPrintLevel(0); + // amg.SetPrintLevel(0); + amg.SetMaxIter(5); dynamic_cast(*pcg).SetPrintLevel( IterativeSolver::PrintLevel().Warnings().Errors().Iterations()); @@ -250,6 +253,7 @@ void CurrentLoad::assembleLoad() // pv.RegisterField("CurrentDensity", &j); // pv.Save(); + // j.ProjectCoefficient(current); // mfem::ParaViewDataCollection paraview_dc("current_density", // j.ParFESpace()->GetParMesh()); // paraview_dc.SetPrefixPath("ParaView"); diff --git a/src/utils/irrotational_projector.cpp b/src/utils/irrotational_projector.cpp index 0ef9e5cb..563e66cd 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -27,7 +27,8 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, pcg.SetRelTol(1e-14); pcg.SetAbsTol(1e-14); pcg.SetMaxIter(200); - pcg.SetPrintLevel(IterativeSolver::PrintLevel().Warnings().Errors()); + pcg.SetKDim(200); + pcg.SetPrintLevel(IterativeSolver::PrintLevel().Warnings().Errors().Iterations().All()); pcg.SetPreconditioner(amg); /// not sure if theres a better way to handle this diff --git a/src/utils/irrotational_projector.hpp b/src/utils/irrotational_projector.hpp index 6d503859..5f51f3a8 100644 --- a/src/utils/irrotational_projector.hpp +++ b/src/utils/irrotational_projector.hpp @@ -99,7 +99,8 @@ class IrrotationalProjector : public mfem::Operator mutable mfem::HypreBoomerAMG amg; // mutable mfem::HyprePCG pcg; - mutable mfem::CGSolver pcg; + mutable mfem::GMRESSolver pcg; + // mutable mfem::CGSolver pcg; mfem::Array ess_bdr, ess_bdr_tdofs; }; diff --git a/test/unit/test_magnetic_load.cpp b/test/unit/test_magnetic_load.cpp index 300cd7bf..537d58c6 100644 --- a/test/unit/test_magnetic_load.cpp +++ b/test/unit/test_magnetic_load.cpp @@ -63,7 +63,9 @@ TEST_CASE("MagneticLoad Value Test") })"_json; mfem::ConstantCoefficient nu(1.0); ///(M_PI*4e-7)); - MagneticLoad load(diff_stack, fes, fields, options, material_library, nu); + MagneticLoad load_0(diff_stack, fes, fields, options, material_library, nu); + + MagneticLoad load(std::move(load_0)); MachInputs inputs; setInputs(load, inputs); From c1a4c24ad3fb2f11dea3e74c1903d6aa6d634d19 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 09:19:58 -0600 Subject: [PATCH 03/72] added nonlinear diffusion integrator for 2D EM --- .../electromagnetics/electromag_integ.cpp | 208 ++++++++++++++++-- .../electromagnetics/electromag_integ.hpp | 40 ++++ test/unit/test_electromag_integ.cpp | 65 ++++++ 3 files changed, 293 insertions(+), 20 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 162bb841..c9ed8d0b 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -85,6 +85,171 @@ double calcMagneticEnergyDoubleDot(ElementTransformation &trans, return d2endB2; } +void NonlinearDiffusionIntegrator::AssembleElementVector( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elvect) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + elvect.SetSize(ndof); + + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix dshapedxt; +#endif + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + + double pointflux_buffer[3]; + Vector pointflux(pointflux_buffer, space_dim); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + if (el.Space() == FunctionSpace::rQk) + { + ir = &RefinedIntRules.Get(el.GetGeomType(), order); + } + else + { + ir = &IntRules.Get(el.GetGeomType(), order); + } + } + + elvect = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double w = alpha * ip.weight / trans.Weight(); + + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + dshapedxt.MultTranspose(elfun, pointflux); + + const double pointflux_norm = pointflux.Norml2(); + const double pointflux_mag = pointflux_norm / trans.Weight(); + + double model_val = model.Eval(trans, ip, pointflux_mag); + + pointflux *= w * model_val; + + dshapedxt.AddMult(pointflux, elvect); + } +} + +void NonlinearDiffusionIntegrator::AssembleElementGrad( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::DenseMatrix &elmat) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + elmat.SetSize(ndof); + elmat = 0.0; + + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix dshapedxt; + DenseMatrix point_flux_2_dot; + Vector scratch; +#endif + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + point_flux_2_dot.SetSize(ndof, space_dim); + pointflux_norm_dot.SetSize(ndof); + + double pointflux_buffer[3]; + Vector pointflux(pointflux_buffer, space_dim); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + if (el.Space() == FunctionSpace::rQk) + { + ir = &RefinedIntRules.Get(el.GetGeomType(), order); + } + else + { + ir = &IntRules.Get(el.GetGeomType(), order); + } + } + + elmat = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + + double w = alpha * ip.weight / trans_weight; + + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + dshapedxt.MultTranspose(elfun, pointflux); + + const double pointflux_norm = pointflux.Norml2(); + + pointflux_norm_dot = 0.0; + dshapedxt.AddMult_a(1.0/pointflux_norm, pointflux, pointflux_norm_dot); + + const double pointflux_mag = pointflux_norm / trans_weight; + pointflux_norm_dot /= trans_weight; + + double model_val = model.Eval(trans, ip, pointflux_mag); + + double model_deriv = model.EvalStateDeriv(trans, ip, pointflux_mag); + pointflux_norm_dot *= model_deriv; + + point_flux_2_dot = dshapedxt; + point_flux_2_dot *= model_val; + + AddMultVWt(pointflux_norm_dot, pointflux, point_flux_2_dot); + point_flux_2_dot *= w; + + AddMultABt(dshapedxt, point_flux_2_dot, elmat); + } +} + void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun, @@ -243,14 +408,14 @@ void CurlCurlNLFIntegrator::AssembleElementGrad( ///////////////////////////////////////////////////////////////////////// if (abs(b_mag) > 1e-14) { - /// calculate curl(N_i) dot curl(A), need to store in a DenseMatrix so - /// we can take outer product of result to generate matrix + /// calculate curl(N_i) dot curl(A), need to store in a DenseMatrix + /// so we can take outer product of result to generate matrix scratch = 0.0; curlshape_dFt.Mult(b_vec, scratch); - /// evaluate the derivative of the material model with respect to the - /// norm of the grid function associated with the model at the point - /// defined by ip, and scale by integration point weight + /// evaluate the derivative of the material model with respect to + /// the norm of the grid function associated with the model at the + /// point defined by ip, and scale by integration point weight double model_deriv = model.EvalStateDeriv(trans, ip, b_mag); model_deriv *= w; model_deriv /= b_mag; @@ -667,9 +832,8 @@ void MagnetizationIntegrator::AssembleElementGrad( if (abs(b_mag) > 1e-14) { /// TODO - is this thread safe? - /// calculate curl(N_i) dot curl(A), need to store in a DenseMatrix so -we - /// can take outer product of result to generate matrix + /// calculate curl(N_i) dot curl(A), need to store in a DenseMatrix + /// so we can take outer product of result to generate matrix temp_vec = 0.0; curlshape_dFt.Mult(b_vec, temp_vec); DenseMatrix temp_matrix(temp_vec.GetData(), ndof, 1); @@ -681,8 +845,8 @@ we curlshape_dFt.Mult(mag_vec, temp_vec2); DenseMatrix temp_matrix2(temp_vec2.GetData(), ndof, 1); - /// evaluate the derivative of the material model with respect to the - /// norm of the grid function associated with the model at the point + /// evaluate the derivative of the material model with respect to + /// the norm of the grid function associated with the model at the point /// defined by ip, and scale by integration point weight double nu_deriv = nu->EvalStateDeriv(trans, ip, b_mag); nu_deriv *= w; @@ -1716,8 +1880,8 @@ double MagneticCoenergyIntegrator::FDintegrateBH( double fd_val; fd_val = integrateBH(ir, trans, old_ip, lower_bound, upper_bound + delta); - fd_val -= integrateBH(ir, trans, old_ip, lower_bound, upper_bound - delta); - return fd_val / (2*delta); + fd_val -= integrateBH(ir, trans, old_ip, lower_bound, upper_bound - +delta); return fd_val / (2*delta); } double MagneticCoenergyIntegrator::RevADintegrateBH( @@ -1748,8 +1912,8 @@ double MagneticCoenergyIntegrator::RevADintegrateBH( double xi = ip.x * (upper_bound - lower_bound); // qp_en += ip.weight * xi / nu->Eval(trans, old_ip, xi); double xi_bar = qp_en_bar * ip.weight / nu->Eval(trans, old_ip, xi); - xi_bar -= (qp_en_bar * ip.weight * xi * nu->EvalStateDeriv(trans, old_ip, -xi) / pow(nu->Eval(trans, old_ip, xi), 2.0)); + xi_bar -= (qp_en_bar * ip.weight * xi * nu->EvalStateDeriv(trans, +old_ip, xi) / pow(nu->Eval(trans, old_ip, xi), 2.0)); // double xi = ip.x * (upper_bound - lower_bound); upper_bound_bar += ip.x*xi_bar; } @@ -2473,7 +2637,8 @@ void nuBNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, // isotrans.WeightRevDiff(PointMat_bar); // PointMat_bar *= weight_bar; // // This is out of order because WeightRevDiff needs to scale - // PointMat_bar first isotrans.JacobianRevDiff(Jac_bar, PointMat_bar); + // PointMat_bar first isotrans.JacobianRevDiff(Jac_bar, + // PointMat_bar); // // code to insert PointMat_bar into elvect; for (int j = 0; j < ndof; ++j) @@ -2707,7 +2872,8 @@ void ThermalSensIntegrator::AssembleRHSElementVect( } } -// void setInputs(DCLossFunctionalIntegrator &integ, const MachInputs &inputs) +// void setInputs(DCLossFunctionalIntegrator &integ, const MachInputs +// &inputs) // { // setValueFromInputs(inputs, "rms_current", integ.rms_current); // } @@ -3332,7 +3498,8 @@ void ForceIntegrator::AssembleElementVector(const mfem::FiniteElement &el, double dBds_bar = force_bar * energy_dot; double energy_dot_bar = force_bar * dBds; - /// const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + /// const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, + /// b_mag); auto energy_double_dot = calcMagneticEnergyDoubleDot(trans, ip, nu, b_mag); b_mag_bar += energy_dot_bar * energy_double_dot; @@ -3590,7 +3757,8 @@ void ForceIntegratorMeshSens::AssembleRHSElementVect( double energy_dot_bar = force_bar * dBds; // double dBds_bar = force_bar; - /// const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + /// const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, + /// b_mag); auto energy_double_dot = calcMagneticEnergyDoubleDot(trans, ip, nu, b_mag); b_mag_bar += energy_dot_bar * energy_double_dot; @@ -3666,8 +3834,8 @@ void ForceIntegratorMeshSens::AssembleRHSElementVect( /// curlshape_dFt.AddMultTranspose(elfun, b_vec); DenseMatrix curlshape_dFt_bar( - dimc, el_ndof); // transposed dimensions of curlshape_dFt so I don't - // have to transpose J later + dimc, el_ndof); // transposed dimensions of curlshape_dFt so I + // don't have to transpose J later MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 348ae0d3..0678c962 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -47,6 +47,46 @@ double calcMagneticEnergyDoubleDot(mfem::ElementTransformation &trans, StateCoefficient &nu, double B); +/// Integrator for (m(u) grad u, grad v) +class NonlinearDiffusionIntegrator : public mfem::NonlinearFormIntegrator +{ +public: + NonlinearDiffusionIntegrator(StateCoefficient &m, double a = 1.0) + : model(m), alpha(a) + { } + + /// Construct the element local residual + /// \param[in] el - the finite element whose residual we want + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - element local state vector + /// \param[out] elvect - element local residual + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elvect) override; + + /// Construct the element local Jacobian + /// \param[in] el - the finite element whose Jacobian we want + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - element local state vector + /// \param[out] elmat - element local Jacobian + void AssembleElementGrad(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::DenseMatrix &elmat) override; + +private: + /// material (thus mesh) dependent model describing electromagnetic behavior + StateCoefficient &model; + /// scales the terms; can be used to move to rhs/lhs + double alpha; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshape, dshapedxt, point_flux_2_dot; + mfem::Vector pointflux_norm_dot; +#endif +}; + /// Integrator for (\nu(u)*curl u, curl v) for Nedelec elements class CurlCurlNLFIntegrator : public mfem::NonlinearFormIntegrator { diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 40979862..e850340a 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -6,6 +6,71 @@ #include "electromag_integ.hpp" #include "electromag_test_data.hpp" +TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") +{ + using namespace mfem; + using namespace electromag_data; + + const int dim = 2; // templating is hard here because mesh constructors + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + FunctionCoefficient A([](const mfem::Vector &x) + { + return x(0); + }); + a.ProjectCoefficient(A); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + NonlinearForm res(&fes); + res.AddDomainIntegrator(new mach::NonlinearDiffusionIntegrator(nu)); + + // initialize the vector that the Jacobian multiplies + GridFunction v(&fes); + // v.ProjectCoefficient(pert); + v.ProjectCoefficient(A); + + // evaluate the Jacobian and compute its product with v + Operator& jac = res.GetGradient(a); + GridFunction jac_v(&fes); + jac.Mult(v, jac_v); + + // now compute the finite-difference approximation... + GridFunction r(&fes), jac_v_fd(&fes); + a.Add(-delta, v); + res.Mult(a, r); + a.Add(2*delta, v); + res.Mult(a, jac_v_fd); + jac_v_fd -= r; + jac_v_fd /= (2*delta); + + for (int i = 0; i < jac_v.Size(); ++i) + { + REQUIRE( jac_v(i) == Approx(jac_v_fd(i)).margin(1e-6) ); + } + } + } +} + TEST_CASE("CurlCurlNLFIntegrator::AssembleElementGrad - linear", "[CurlCurlNLFIntegrator]") { From 2951b79021c8a722b5ef2a770d4372b6468d8b04 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 10:07:48 -0600 Subject: [PATCH 04/72] make format --- src/common/material_library.cpp | 74 +++++++++---------- src/physics/electromagnetics/current_load.cpp | 2 +- src/utils/irrotational_projector.cpp | 3 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/common/material_library.cpp b/src/common/material_library.cpp index a91d7423..b8b5f386 100644 --- a/src/common/material_library.cpp +++ b/src/common/material_library.cpp @@ -80,45 +80,43 @@ const nlohmann::json material_library{ {"alpha", 1.286}}}, {"hiperco50", {{"B", - // {0.0, - // 0.0, - // 0.0, - // 0.0, - // 1.424590497285806, - // 1.879802197738017, - // 2.089177970766961, - // 2.184851867536579, - // 2.229448683, - // 2.264761102805212, - // 2.302883806114454, - // 7.290145827, - // 7.290145827, - // 7.290145827, - // 7.290145827}}, - {0. , 0. , 0. , 0. , 0.2 , 0.3 , 0.4 , 0.5 , - 0.6 , 0.7 , 0.8 , 0.9 , 1. , 1.1 , 1.1976, 1.3 , - 1.4 , 1.4775, 1.6 , 1.7119, 1.8 , 1.9 , 2. , 2.1 , - 2.1868, 2.25 , 2.3 , 2.36 , 2.4 , 2.5 , 2.5 , 2.5 , - 2.5}}, + // {0.0, + // 0.0, + // 0.0, + // 0.0, + // 1.424590497285806, + // 1.879802197738017, + // 2.089177970766961, + // 2.184851867536579, + // 2.229448683, + // 2.264761102805212, + // 2.302883806114454, + // 7.290145827, + // 7.290145827, + // 7.290145827, + // 7.290145827}}, + {0., 0., 0., 0., 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, + 0.9, 1., 1.1, 1.1976, 1.3, 1.4, 1.4775, 1.6, 1.7119, 1.8, 1.9, + 2., 2.1, 2.1868, 2.25, 2.3, 2.36, 2.4, 2.5, 2.5, 2.5, 2.5}}, {"H", - // {0.0, - // 8.993187962999999, - // 10.978287105, - // 83.247282676, - // 211.230768762, - // 458.336047457, - // 1159.131573849, - // 2773.494054824, - // 1.342303920594017e6, - // 2.675328620250853e6, - // 3.9982409587815357e6}}, - {-8.45567148e-16, 1.52187154e+01, 2.44069859e+01, 2.87543851e+01, - 3.28574240e+01, 3.53931188e+01, 3.83409008e+01, 4.10642780e+01, - 4.38435871e+01, 4.64801737e+01, 4.96317182e+01, 5.29859037e+01, - 5.66857479e+01, 6.15933673e+01, 6.64821406e+01, 7.75969027e+01, - 9.75330122e+01, 1.21434401e+02, 1.52150611e+02, 1.94912815e+02, - 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, - 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, + // {0.0, + // 8.993187962999999, + // 10.978287105, + // 83.247282676, + // 211.230768762, + // 458.336047457, + // 1159.131573849, + // 2773.494054824, + // 1.342303920594017e6, + // 2.675328620250853e6, + // 3.9982409587815357e6}}, + {-8.45567148e-16, 1.52187154e+01, 2.44069859e+01, 2.87543851e+01, + 3.28574240e+01, 3.53931188e+01, 3.83409008e+01, 4.10642780e+01, + 4.38435871e+01, 4.64801737e+01, 4.96317182e+01, 5.29859037e+01, + 5.66857479e+01, 6.15933673e+01, 6.64821406e+01, 7.75969027e+01, + 9.75330122e+01, 1.21434401e+02, 1.52150611e+02, 1.94912815e+02, + 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, + 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, 3.97887360e+04}}, {"mu_r", 750}, {"rho", 8110.0}, diff --git a/src/physics/electromagnetics/current_load.cpp b/src/physics/electromagnetics/current_load.cpp index d2be15db..ba6f3233 100644 --- a/src/physics/electromagnetics/current_load.cpp +++ b/src/physics/electromagnetics/current_load.cpp @@ -196,7 +196,7 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, pcg(constructLinearSolver(fes.GetComm(), {{"type", "gmres"}, {"kdim", 500}, - // {{"type", "pcg"}, + // {{"type", "pcg"}, {"reltol", 1e-12}, {"abstol", 1e-12}, {"maxiter", 500}, diff --git a/src/utils/irrotational_projector.cpp b/src/utils/irrotational_projector.cpp index 563e66cd..afc53a2a 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -28,7 +28,8 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, pcg.SetAbsTol(1e-14); pcg.SetMaxIter(200); pcg.SetKDim(200); - pcg.SetPrintLevel(IterativeSolver::PrintLevel().Warnings().Errors().Iterations().All()); + pcg.SetPrintLevel( + IterativeSolver::PrintLevel().Warnings().Errors().Iterations().All()); pcg.SetPreconditioner(amg); /// not sure if theres a better way to handle this From f5f39b916d52d1cb88c7c397bd77c11b68d3d999 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 10:13:23 -0600 Subject: [PATCH 05/72] update nonlinear diffusion test --- test/unit/test_electromag_integ.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index e850340a..f3a7ae38 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -33,21 +33,13 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") FunctionCoefficient pert(randState); a.ProjectCoefficient(pert); - FunctionCoefficient A([](const mfem::Vector &x) - { - return x(0); - }); - a.ProjectCoefficient(A); - NonLinearCoefficient nu; - // LinearCoefficient nu; NonlinearForm res(&fes); res.AddDomainIntegrator(new mach::NonlinearDiffusionIntegrator(nu)); // initialize the vector that the Jacobian multiplies GridFunction v(&fes); - // v.ProjectCoefficient(pert); - v.ProjectCoefficient(A); + v.ProjectCoefficient(pert); // evaluate the Jacobian and compute its product with v Operator& jac = res.GetGradient(a); @@ -65,7 +57,7 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") for (int i = 0; i < jac_v.Size(); ++i) { - REQUIRE( jac_v(i) == Approx(jac_v_fd(i)).margin(1e-6) ); + REQUIRE(jac_v(i) == Approx(jac_v_fd(i)).margin(1e-6)); } } } From f82025a7f3429f8d2bc55f7047892e2aa10c88c1 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 10:13:37 -0600 Subject: [PATCH 06/72] ninja format --- src/physics/electromagnetics/electromag_integ.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index c9ed8d0b..7d4dd09a 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -230,13 +230,13 @@ void NonlinearDiffusionIntegrator::AssembleElementGrad( const double pointflux_norm = pointflux.Norml2(); pointflux_norm_dot = 0.0; - dshapedxt.AddMult_a(1.0/pointflux_norm, pointflux, pointflux_norm_dot); + dshapedxt.AddMult_a(1.0 / pointflux_norm, pointflux, pointflux_norm_dot); const double pointflux_mag = pointflux_norm / trans_weight; pointflux_norm_dot /= trans_weight; double model_val = model.Eval(trans, ip, pointflux_mag); - + double model_deriv = model.EvalStateDeriv(trans, ip, pointflux_mag); pointflux_norm_dot *= model_deriv; @@ -846,7 +846,8 @@ void MagnetizationIntegrator::AssembleElementGrad( DenseMatrix temp_matrix2(temp_vec2.GetData(), ndof, 1); /// evaluate the derivative of the material model with respect to - /// the norm of the grid function associated with the model at the point + /// the norm of the grid function associated with the model at the +point /// defined by ip, and scale by integration point weight double nu_deriv = nu->EvalStateDeriv(trans, ip, b_mag); nu_deriv *= w; From d3777bfbe849d9df111654fec90b06c0dadd3964 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 13:44:11 -0600 Subject: [PATCH 07/72] magnetostatic_box2d test works, is significantly more performant than the 3d version, and can actually converge a nonlinear residual --- .../current_source_functions.cpp | 237 ++++++++++++++++++ .../current_source_functions.hpp | 40 +++ .../magnetostatic_residual.cpp | 66 +++-- .../magnetostatic_residual.hpp | 7 +- src/physics/mach_load.hpp | 6 +- test/regression/CMakeLists.txt | 1 + test/regression/test_magnetostatic_box.cpp | 28 ++- test/unit/test_mach_load.cpp | 2 +- 8 files changed, 359 insertions(+), 28 deletions(-) diff --git a/src/physics/electromagnetics/current_source_functions.cpp b/src/physics/electromagnetics/current_source_functions.cpp index ea6c5e18..7585d828 100644 --- a/src/physics/electromagnetics/current_source_functions.cpp +++ b/src/physics/electromagnetics/current_source_functions.cpp @@ -211,6 +211,24 @@ void box2_current(const xdouble *x, xdouble *J) J[2] = 6 * y; } +template +xdouble box1_current2D(const xdouble *x) +{ + auto y = x[1] - .5; + + // J[2] = -current_density*6*y*(1/(M_PI*4e-7)); // for real scaled problem + return -6 * y; +} + +template +xdouble box2_current2D(const xdouble *x) +{ + auto y = x[1] - .5; + + // J[2] = current_density*6*y*(1/(M_PI*4e-7)); // for real scaled problem + return 6 * y; +} + /// function to get the sign of a number template int sgn(T val) @@ -536,6 +554,61 @@ void box2CurrentSourceRevDiff(adept::Stack &diff_stack, source_jac.MultTranspose(V_bar, x_bar); } +double box1CurrentSource2D(const mfem::Vector &x) +{ + return box1_current2D(x.GetData()); +} + +void box1CurrentSource2DRevDiff(adept::Stack &diff_stack, + const mfem::Vector &x, + const double &J_bar, + mfem::Vector &x_bar) +{ + // mfem::DenseMatrix source_jac(3); + // // declare vectors of active input variables + // std::vector x_a(x.Size()); + // // copy data from mfem::Vector + // adept::set_values(x_a.data(), x.Size(), x.GetData()); + // // start recording + // diff_stack.new_recording(); + // // the depedent variable must be declared after the recording + // std::vector J_a(x.Size()); + // box1_current(x_a.data(), J_a.data()); + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(J_a.data(), x.Size()); + // // calculate the jacobian w.r.t state vaiables + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); +} + +double box2CurrentSource2D(const mfem::Vector &x) +{ + return box2_current2D(x.GetData()); +} + +void box2CurrentSource2DRevDiff(adept::Stack &diff_stack, + const mfem::Vector &x, + const double &J_bar, + mfem::Vector &x_bar) +{ + // mfem::DenseMatrix source_jac(3); + // // declare vectors of active input variables + // std::vector x_a(x.Size()); + // // copy data from mfem::Vector + // adept::set_values(x_a.data(), x.Size(), x.GetData()); + // // start recording + // diff_stack.new_recording(); + // // the depedent variable must be declared after the recording + // adouble J_a = box2_current2D(x_a.data()); + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(J_a); + // // calculate the jacobian w.r.t state vaiables + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); +} + void team13CurrentSource(const mfem::Vector &x, mfem::Vector &J) { team13_current(x.GetData(), J.GetData()); @@ -903,4 +976,168 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } } +void CurrentDensityCoefficient2D::cacheCurrentDensity() +{ + for (auto &[group, coeff] : group_map) + { + cached_inputs.at(group) = coeff.constant; + } +} + +void CurrentDensityCoefficient2D::zeroCurrentDensity() +{ + for (auto &[group, coeff] : group_map) + { + coeff.constant = 0.0; + } +} + +void CurrentDensityCoefficient2D::resetCurrentDensityFromCache() +{ + for (auto &[group, value] : cached_inputs) + { + group_map.at(group).constant = value; + } +} + +bool setInputs(CurrentDensityCoefficient2D ¤t, const MachInputs &inputs) +{ + bool updated = false; + for (auto &[group, coeff] : current.group_map) + { + auto old_const = coeff.constant; + std::string cd_group_id = "current_density:" + group; + setValueFromInputs(inputs, cd_group_id, coeff.constant); + if (coeff.constant != old_const) + { + updated = true; + } + } + + for (auto &[input, value] : current.cached_inputs) + { + auto old_value = value; + setValueFromInputs(inputs, input, value); + if (value != old_value) + { + updated = true; + } + } + return updated; +} + +double CurrentDensityCoefficient2D::Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip) +{ + return -1.0 * current_coeff.Eval(trans, ip); +} + +void CurrentDensityCoefficient2D::EvalRevDiff( + double Q_bar, + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + mfem::DenseMatrix &PointMat_bar) +{ + Q_bar *= 1.0; + current_coeff.EvalRevDiff(Q_bar, trans, ip, PointMat_bar); +} + +CurrentDensityCoefficient2D::CurrentDensityCoefficient2D( + adept::Stack &diff_stack, + const nlohmann::json ¤t_options) +{ + for (auto &[group, group_details] : current_options.items()) + { + group_map.emplace(group, mfem::ConstantCoefficient{1.0}); + auto &group_coeff = group_map.at(group); + + cached_inputs.emplace(group, 0.0); + + for (auto &[source, attrs] : group_details.items()) + { + if (source == "z") + { + for (auto &attr : attrs) + { + source_coeffs.emplace( + attr, + mfem::FunctionCoefficient([](const mfem::Vector &x) + { return 1.0; }, + [](const mfem::Vector &x, + const double Q_bar, + mfem::Vector &x_bar) {})); + auto &source_coeff = source_coeffs.at(attr); + + current_coeff.addCoefficient( + attr, + std::make_unique(group_coeff, + source_coeff)); + } + } + else if (source == "-z") + { + for (auto &attr : attrs) + { + // source_coeffs.emplace(attr, mfem::FunctionCoefficient(-1.0)); + source_coeffs.emplace( + attr, + mfem::FunctionCoefficient([](const mfem::Vector &) + { return -1.0; }, + [](const mfem::Vector &x, + const double Q_bar, + mfem::Vector &x_bar) {})); + auto &source_coeff = source_coeffs.at(attr); + + current_coeff.addCoefficient( + attr, + std::make_unique(group_coeff, + source_coeff)); + } + } + else if (source == "box1") + { + for (auto &attr : attrs) + { + source_coeffs.emplace(attr, + mfem::FunctionCoefficient( + box1CurrentSource2D, + [&diff_stack](const mfem::Vector &x, + const double &J_bar, + mfem::Vector &x_bar) { + box1CurrentSource2DRevDiff( + diff_stack, x, J_bar, x_bar); + })); + auto &source_coeff = source_coeffs.at(attr); + + current_coeff.addCoefficient( + attr, + std::make_unique(group_coeff, + source_coeff)); + } + } + else if (source == "box2") + { + for (auto &attr : attrs) + { + source_coeffs.emplace(attr, + mfem::FunctionCoefficient( + box2CurrentSource2D, + [&diff_stack](const mfem::Vector &x, + const double &J_bar, + mfem::Vector &x_bar) { + box2CurrentSource2DRevDiff( + diff_stack, x, J_bar, x_bar); + })); + auto &source_coeff = source_coeffs.at(attr); + + current_coeff.addCoefficient( + attr, + std::make_unique(group_coeff, + source_coeff)); + } + } + } + } +} + } // namespace mach diff --git a/src/physics/electromagnetics/current_source_functions.hpp b/src/physics/electromagnetics/current_source_functions.hpp index 55b055c2..bd117443 100644 --- a/src/physics/electromagnetics/current_source_functions.hpp +++ b/src/physics/electromagnetics/current_source_functions.hpp @@ -56,6 +56,46 @@ class CurrentDensityCoefficient : public mfem::VectorCoefficient std::map cached_inputs; }; +class CurrentDensityCoefficient2D : public mfem::Coefficient +{ +public: + /// Cache the currently set current density values for each current group + void cacheCurrentDensity(); + /// Set the current density for each current group to zero + void zeroCurrentDensity(); + /// Reset the current density for each current group to the values stored + /// in the cache + /// \note If values have not previously been cached, defaults to zero + void resetCurrentDensityFromCache(); + + /// Variation on setInputs that returns true if any inputs were actually + /// updated + friend bool setInputs(CurrentDensityCoefficient2D ¤t, + const MachInputs &inputs); + + double Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip) override; + + void EvalRevDiff(double Q_bar, + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + mfem::DenseMatrix &PointMat_bar) override; + + CurrentDensityCoefficient2D(adept::Stack &diff_stack, + const nlohmann::json ¤t_options); + +private: + /// The underlying coefficient that does all the heavy lifting + MeshDependentCoefficient current_coeff; + /// Map that holds coefficients for each current group so that the scalar + /// input may be set for each group + std::map group_map; + /// Map that owns all of the underlying source coefficients + std::map source_coeffs; + /// Inputs to be passed by reference to source-wrapping lambdas + std::map cached_inputs; +}; + // /// Construct vector coefficient that describes the current source direction // /// \param[in] options - JSON options dictionary that maps mesh element // /// attributes to known current source functions diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 87c0d605..cdcb49f2 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -2,6 +2,8 @@ #include #include "adept.h" +#include "electromag_integ.hpp" +#include "mach_linearform.hpp" #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -9,6 +11,7 @@ #include "magnetostatic_load.hpp" #include "magnetostatic_residual.hpp" +#include "utils.hpp" namespace { @@ -16,10 +19,21 @@ std::unique_ptr constructPreconditioner( mfem::ParFiniteElementSpace &fes, const nlohmann::json &prec_options) { - auto ams = std::make_unique(&fes); - ams->SetPrintLevel(prec_options["printlevel"].get()); - ams->SetSingularProblem(); - return ams; + auto prec_type = prec_options["type"].get(); + if (prec_type == "hypreams") + { + auto ams = std::make_unique(&fes); + ams->SetPrintLevel(prec_options["printlevel"].get()); + ams->SetSingularProblem(); + return ams; + } + else if (prec_type == "hypreboomeramg") + { + auto amg = std::make_unique(); + amg->SetPrintLevel(prec_options["printlevel"].get()); + return amg; + } + return nullptr; } } // namespace @@ -34,13 +48,13 @@ int getSize(const MagnetostaticResidual &residual) void setInputs(MagnetostaticResidual &residual, const mach::MachInputs &inputs) { setInputs(residual.res, inputs); - setInputs(residual.load, inputs); + setInputs(*residual.load, inputs); } void setOptions(MagnetostaticResidual &residual, const nlohmann::json &options) { setOptions(residual.res, options); - setOptions(residual.load, options); + setOptions(*residual.load, options); } void evaluate(MagnetostaticResidual &residual, @@ -48,8 +62,8 @@ void evaluate(MagnetostaticResidual &residual, mfem::Vector &res_vec) { evaluate(residual.res, inputs, res_vec); - setInputs(residual.load, inputs); - addLoad(residual.load, res_vec); + setInputs(*residual.load, inputs); + addLoad(*residual.load, res_vec); // mfem::Vector state; // setVectorFromInputs(inputs, "state", state); @@ -93,7 +107,7 @@ double jacobianVectorProduct(MagnetostaticResidual &residual, const std::string &wrt) { auto res_dot = jacobianVectorProduct(residual.res, wrt_dot, wrt); - res_dot += jacobianVectorProduct(residual.load, wrt_dot, wrt); + res_dot += jacobianVectorProduct(*residual.load, wrt_dot, wrt); return res_dot; } @@ -103,7 +117,7 @@ void jacobianVectorProduct(MagnetostaticResidual &residual, mfem::Vector &res_dot) { jacobianVectorProduct(residual.res, wrt_dot, wrt, res_dot); - jacobianVectorProduct(residual.load, wrt_dot, wrt, res_dot); + jacobianVectorProduct(*residual.load, wrt_dot, wrt, res_dot); } double vectorJacobianProduct(MagnetostaticResidual &residual, @@ -111,7 +125,7 @@ double vectorJacobianProduct(MagnetostaticResidual &residual, const std::string &wrt) { auto wrt_bar = vectorJacobianProduct(residual.res, res_bar, wrt); - wrt_bar += vectorJacobianProduct(residual.load, res_bar, wrt); + wrt_bar += vectorJacobianProduct(*residual.load, res_bar, wrt); return wrt_bar; } @@ -121,7 +135,7 @@ void vectorJacobianProduct(MagnetostaticResidual &residual, mfem::Vector &wrt_bar) { vectorJacobianProduct(residual.res, res_bar, wrt, wrt_bar); - vectorJacobianProduct(residual.load, res_bar, wrt, wrt_bar); + vectorJacobianProduct(*residual.load, res_bar, wrt, wrt_bar); } mfem::Solver *getPreconditioner(MagnetostaticResidual &residual) @@ -137,10 +151,34 @@ MagnetostaticResidual::MagnetostaticResidual( const nlohmann::json &materials, StateCoefficient &nu) : res(fes, fields), - load(diff_stack, fes, fields, options, materials, nu), + // load(diff_stack, fes, fields, options, materials, nu), prec(constructPreconditioner(fes, options["lin-prec"])) { - res.addDomainIntegrator(new CurlCurlNLFIntegrator(nu)); + auto *mesh = fes.GetParMesh(); + auto space_dim = mesh->SpaceDimension(); + if (space_dim == 3) + { + res.addDomainIntegrator(new CurlCurlNLFIntegrator(nu)); + load = std::make_unique( + MagnetostaticLoad(diff_stack, fes, fields, options, materials, nu)); + } + else if (space_dim == 2) + { + res.addDomainIntegrator(new NonlinearDiffusionIntegrator(nu)); + + MachLinearForm linear_form(fes, fields); + current_coeff = std::make_unique( + diff_stack, options["current"]); + linear_form.addDomainIntegrator( + new mfem::DomainLFIntegrator(*current_coeff)); + + load = std::make_unique(std::move(linear_form)); + } + else + { + throw MachException( + "Invalid mesh dimension for Magnetostatic Residual!\n"); + } } } // namespace mach diff --git a/src/physics/electromagnetics/magnetostatic_residual.hpp b/src/physics/electromagnetics/magnetostatic_residual.hpp index 103bb7d4..8f59cff4 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.hpp +++ b/src/physics/electromagnetics/magnetostatic_residual.hpp @@ -3,9 +3,11 @@ #include #include +#include #include #include "adept.h" +#include "mach_load.hpp" #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -80,7 +82,10 @@ class MagnetostaticResidual final /// Nonlinear form that handles the curl curl term of the weak form MachNonlinearForm res; /// Load vector for current and magnetic sources - MagnetostaticLoad load; + // MagnetostaticLoad load; + std::unique_ptr load; + std::unique_ptr current_coeff; + /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; }; diff --git a/src/physics/mach_load.hpp b/src/physics/mach_load.hpp index 00b669c8..e3c2358e 100644 --- a/src/physics/mach_load.hpp +++ b/src/physics/mach_load.hpp @@ -79,7 +79,7 @@ class MachLoad final mfem::Vector &wrt_bar); template - MachLoad(T &x) : self_(new model(x)) + MachLoad(T x) : self_(new model(x)) { } private: @@ -106,7 +106,7 @@ class MachLoad final class model final : public concept_t { public: - model(T &x) : data_(x) { } + model(T &x) : data_(std::move(x)) { } void setInputs_(const MachInputs &inputs) override { setInputs(data_, inputs); @@ -139,7 +139,7 @@ class MachLoad final vectorJacobianProduct(data_, res_bar, wrt, wrt_bar); } - T &data_; + T data_; }; std::unique_ptr self_; diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index 509177b7..0c1ccf6e 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -20,6 +20,7 @@ set(REGRESSION_TEST_SRCS test_steady_thermal_cube test_mach_inputs test_magnetostatic_box + test_magnetostatic_box2d test_meshmovement_box test_meshmovement_annulus test_ac_loss diff --git a/test/regression/test_magnetostatic_box.cpp b/test/regression/test_magnetostatic_box.cpp index a90f1f31..52e7c933 100644 --- a/test/regression/test_magnetostatic_box.cpp +++ b/test/regression/test_magnetostatic_box.cpp @@ -32,7 +32,7 @@ auto options = R"( }, "lin-solver": { "type": "minres", - "printlevel": 0, + "printlevel": 1, "maxiter": 100, "abstol": 1e-14, "reltol": 1e-14 @@ -43,7 +43,7 @@ auto options = R"( }, "nonlin-solver": { "type": "newton", - "printlevel": 3, + "printlevel": 1, "maxiter": 15, "reltol": 1e-10, "abstol": 1e-9 @@ -110,10 +110,10 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", /// number of elements in Z direction auto nz = 2; - for (int order = 1; order <= 1; ++order) + for (int order = 1; order <= 4; ++order) { options["space-dis"]["degree"] = order; - int nxy = 4; + int nxy = 1; for (int ref = 1; ref <= 1; ++ref) { nxy *= 2; @@ -127,24 +127,34 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", /// Set initial/boundary conditions solver.setState(aexact, state_tv); + + /// Log initial condition + ParaViewLogger logger_init("2d_magnetostatic_initND", &state.mesh()); + logger_init.registerField("state", state.gridFunc()); + logger_init.saveState(state_tv, "state", 0, 0.0, 0); + solver.solveForState(state_tv); + ParaViewLogger logger("2d_magnetostaticND", &state.mesh()); + logger.registerField("state", state.gridFunc()); + logger.saveState(state_tv, "state", 0, 0.0, 0); + /// Compute state error and check against target error double error = solver.calcStateError(aexact, state_tv); std::cout.precision(10); std::cout << "error: " << error << "\n"; - // REQUIRE(error == Approx(target_error[order-1][ref - 1]).margin(1e-10)); + REQUIRE(error == Approx(target_error[order-1][ref - 1]).margin(1e-10)); /// Calculate the magnetic energy and check against target energy solver.createOutput("energy"); MachInputs inputs{{"state", state_tv}}; double energy = solver.calcOutput("energy", inputs); std::cout << "energy: " << energy << "\n"; - // REQUIRE(energy == Approx(target_energy[order-1][ref - 1]).margin(1e-10)); + REQUIRE(energy == Approx(target_energy[order-1][ref - 1]).margin(1e-10)); - solver.solveForState({{"current_density:box", 2.0}}, state_tv); - solver.solveForState({{"current_density:box", 3.0}}, state_tv); - solver.solveForState({{"current_density:box", 4.0}}, state_tv); + // solver.solveForState({{"current_density:box", 2.0}}, state_tv); + // solver.solveForState({{"current_density:box", 3.0}}, state_tv); + // solver.solveForState({{"current_density:box", 4.0}}, state_tv); } } } diff --git a/test/unit/test_mach_load.cpp b/test/unit/test_mach_load.cpp index 7b280e15..5bc67155 100644 --- a/test/unit/test_mach_load.cpp +++ b/test/unit/test_mach_load.cpp @@ -83,7 +83,7 @@ TEST_CASE("MachInputs Scalar Input Test", MachLinearForm lf(fes, fields); lf.addDomainIntegrator(new TestMachLoadIntegrator); - MachLoad ml(lf); + MachLoad ml(std::move(lf)); auto inputs = MachInputs({ {"test_val", 5.0} From 2ddd2cc6e5986ced0f1c9a985edbfb79427cf645 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 18 Jul 2022 13:44:33 -0600 Subject: [PATCH 08/72] adding magnetostatic_box2d test case --- test/regression/test_magnetostatic_box2d.cpp | 206 +++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 test/regression/test_magnetostatic_box2d.cpp diff --git a/test/regression/test_magnetostatic_box2d.cpp b/test/regression/test_magnetostatic_box2d.cpp new file mode 100644 index 00000000..80830e96 --- /dev/null +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -0,0 +1,206 @@ +#include +#include + +#include "catch.hpp" +#include "nlohmann/json.hpp" +#include "mfem.hpp" + +#include "magnetostatic.hpp" + +using namespace std; +using namespace mfem; +using namespace mach; + +// Provide the options explicitly for regression tests +auto options = R"( +{ + "silent": false, + "print-options": false, + "problem": "box", + "space-dis": { + "basis-type": "h1", + "degree": 1 + }, + "time-dis": { + "steady": true, + "steady-abstol": 1e-10, + "steady-reltol": 1e-10, + "ode-solver": "PTC", + "t-final": 100, + "dt": 1, + "max-iter": 5 + }, + "lin-solver": { + "type": "minres", + "printlevel": 1, + "maxiter": 100, + "abstol": 1e-14, + "reltol": 1e-14 + }, + "lin-prec": { + "type": "hypreboomeramg", + "printlevel": 0 + }, + "nonlin-solver": { + "type": "newton", + "printlevel": 1, + "maxiter": 15, + "reltol": 1e-10, + "abstol": 1e-9 + }, + "components": { + "attr1": { + "material": "box1", + "attr": 1, + "linear": true + }, + "attr2": { + "material": "box2", + "attr": 2, + "linear": true + } + }, + "current": { + "box": { + "box1": [1], + "box2": [2] + } + }, + "bcs": { + "essential": "all" + } +})"_json; + +/// \brief Exact solution for magnetic vector potential +/// \param[in] x - coordinate of the point at which the state is needed +///return z component of magnetic vector potential +double aexact(const Vector &x); + +/// Generate mesh +/// \param[in] nxy - number of nodes in the x and y directions +std::unique_ptr buildMesh(int nxy); + +TEST_CASE("Magnetostatic Box Solver Regression Test", + "[Magnetostatic-Box]") +{ + // define the target state solution error + std::vector> target_error = { + // nxy = 2, nxy = 4, nyx = 8, nyx = 16, nxy = 32 + {0.03446617612, 0.0, 0.0, 0.0, 0.0}, // p = 1 + {0.004470003778, 0.0, 0.0, 0.0, 0.0}, // p = 2 + {0.0, 0.0, 0.0, 0.0, 0.0}, // p = 3 + {0.0, 0.0, 0.0, 0.0, 0.0} // p = 4 + }; + + // // define the target computed energy + // std::vector> target_energy = { + // {0.0456124231, 0.0, 0.0, 0.0}, + // {0.05807012599, 0.0, 0.0, 0.0}, + // {0.05629189119, 0.0, 0.0, 0.0}, + // {0.05625, 0.0, 0.0, 0.0} + // }; + + for (int order = 1; order <= 4; ++order) + { + options["space-dis"]["degree"] = order; + int nxy = 1; + for (int ref = 1; ref <= 1; ++ref) + { + nxy *= 2; + DYNAMIC_SECTION("...for order " << order << " and mesh sizing nxy = " << nxy) + { + // construct the solver, set the initial condition, and solve + unique_ptr smesh = buildMesh(nxy); + MagnetostaticSolver solver(MPI_COMM_WORLD, options, std::move(smesh)); + mfem::Vector state_tv(solver.getStateSize()); + + auto &state = solver.getState(); + + /// Set initial/boundary conditions + solver.setState(aexact, state_tv); + + /// Log initial condition + ParaViewLogger logger_init("2d_magnetostatic_init", &state.mesh()); + logger_init.registerField("h1_state", state.gridFunc()); + logger_init.saveState(state_tv, "h1_state", 0, 0.0, 0); + + solver.solveForState(state_tv); + + // state.distributeSharedDofs(state_tv); + ParaViewLogger logger("2d_magnetostatic", &state.mesh()); + logger.registerField("h1_state", state.gridFunc()); + logger.saveState(state_tv, "h1_state", 0, 0.0, 0); + + /// Compute state error and check against target error + double error = solver.calcStateError(aexact, state_tv); + std::cout.precision(10); + std::cout << "error: " << error << "\n"; + REQUIRE(error == Approx(target_error[order-1][ref - 1]).margin(1e-10)); + + // /// Calculate the magnetic energy and check against target energy + // solver.createOutput("energy"); + // MachInputs inputs{{"state", state_tv}}; + // double energy = solver.calcOutput("energy", inputs); + // std::cout << "energy: " << energy << "\n"; + // // REQUIRE(energy == Approx(target_energy[order-1][ref - 1]).margin(1e-10)); + + // solver.solveForState({{"current_density:box", 2.0}}, state_tv); + // solver.solveForState({{"current_density:box", 3.0}}, state_tv); + // solver.solveForState({{"current_density:box", 4.0}}, state_tv); + } + } + } +} + +double aexact(const Vector &x) +{ + double y = x(1) - 0.5; + if ( x(1) <= .5) + { + return y*y*y; + } + else + { + return -y*y*y; + } +} + +unique_ptr buildMesh(int nxy) +{ + // generate a simple tet mesh + std::unique_ptr mesh( + new Mesh(Mesh::MakeCartesian2D(nxy, nxy, + Element::TRIANGLE))); + + // assign attributes to top and bottom sides + for (int i = 0; i < mesh->GetNE(); ++i) + { + Element *elem = mesh->GetElement(i); + + Array verts; + elem->GetVertices(verts); + + bool below = true; + for (int i = 0; i < verts.Size(); ++i) + { + auto vtx = mesh->GetVertex(verts[i]); + if (vtx[1] <= 0.5) + { + below = below & true; + } + else + { + below = below & false; + } + } + if (below) + { + elem->SetAttribute(1); + } + else + { + elem->SetAttribute(2); + } + } + return mesh; +} From 782ad8746100abd3e045ce9b62dcb01c11eb9c6b Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 19 Jul 2022 18:46:37 -0600 Subject: [PATCH 09/72] 2d magnet implementation seems to be working, matches the results seen with 3D magnets --- .../electromagnetics/electromag_integ.cpp | 92 +++++++- .../electromagnetics/electromag_integ.hpp | 24 ++ .../magnetic_source_functions.cpp | 213 +++++++++++------- .../electromagnetics/magnetostatic.cpp | 4 + .../magnetostatic_residual.cpp | 21 +- .../magnetostatic_residual.hpp | 2 + src/utils/l2_transfer_operator.cpp | 73 +++++- test/regression/CMakeLists.txt | 1 + 8 files changed, 345 insertions(+), 85 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 7d4dd09a..4b8e0b1e 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -140,7 +140,9 @@ void NonlinearDiffusionIntegrator::AssembleElementVector( const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - double w = alpha * ip.weight / trans.Weight(); + double trans_weight = trans.Weight(); + + double w = alpha * ip.weight / trans_weight; el.CalcDShape(ip, dshape); Mult(dshape, trans.AdjugateJacobian(), dshapedxt); @@ -148,7 +150,7 @@ void NonlinearDiffusionIntegrator::AssembleElementVector( dshapedxt.MultTranspose(elfun, pointflux); const double pointflux_norm = pointflux.Norml2(); - const double pointflux_mag = pointflux_norm / trans.Weight(); + const double pointflux_mag = pointflux_norm / trans_weight; double model_val = model.Eval(trans, ip, pointflux_mag); @@ -243,13 +245,97 @@ void NonlinearDiffusionIntegrator::AssembleElementGrad( point_flux_2_dot = dshapedxt; point_flux_2_dot *= model_val; - AddMultVWt(pointflux_norm_dot, pointflux, point_flux_2_dot); + if (abs(pointflux_norm) > 1e-14) + { + AddMultVWt(pointflux_norm_dot, pointflux, point_flux_2_dot); + } point_flux_2_dot *= w; AddMultABt(dshapedxt, point_flux_2_dot, elmat); } } +void MagnetizationSource2DIntegrator::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + elvect.SetSize(ndof); + + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + if (space_dim != 2) + { + mfem_error( + "MagnetizationSource2DIntegrator only supports 2D space dim!\n"); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix dshapedxt; + Vector scratch; +#endif + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + scratch.SetSize(ndof); + + double mag_flux_buffer[3]; + Vector mag_flux(mag_flux_buffer, space_dim); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + if (el.Space() == FunctionSpace::rQk) + { + ir = &RefinedIntRules.Get(el.GetGeomType(), order); + } + else + { + ir = &IntRules.Get(el.GetGeomType(), order); + } + } + + elvect = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double w = alpha * ip.weight; + + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + M.Eval(mag_flux, trans, ip); + mag_flux *= w; + + scratch = 0.0; + Vector grad_column; + dshapedxt.GetColumnReference(0, grad_column); + scratch.Add(mag_flux(1), grad_column); + + dshapedxt.GetColumnReference(1, grad_column); + scratch.Add(-mag_flux(0), grad_column); + + elvect += scratch; + } +} + void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun, diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 0678c962..45315977 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -87,6 +87,30 @@ class NonlinearDiffusionIntegrator : public mfem::NonlinearFormIntegrator #endif }; +class MagnetizationSource2DIntegrator : public mfem::LinearFormIntegrator +{ +public: + MagnetizationSource2DIntegrator(mfem::VectorCoefficient &M, + double alpha = 1.0) + : M(M), alpha(alpha) + { } + + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) override; + +private: + /// vector coefficient from linear form + mfem::VectorCoefficient &M; + /// scaling term if the linear form has a negative sign in the residual + const double alpha; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshape, dshapedxt; + mfem::Vector scratch; +#endif +}; + /// Integrator for (\nu(u)*curl u, curl v) for Nedelec elements class CurlCurlNLFIntegrator : public mfem::NonlinearFormIntegrator { diff --git a/src/physics/electromagnetics/magnetic_source_functions.cpp b/src/physics/electromagnetics/magnetic_source_functions.cpp index 56ce41a1..4113db6e 100644 --- a/src/physics/electromagnetics/magnetic_source_functions.cpp +++ b/src/physics/electromagnetics/magnetic_source_functions.cpp @@ -11,7 +11,8 @@ namespace using adept::adouble; template -void north_magnetization(const xdouble &remnant_flux, +void north_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { @@ -21,11 +22,15 @@ void north_magnetization(const xdouble &remnant_flux, xdouble norm_r = sqrt(r[0] * r[0] + r[1] * r[1]); M[0] = r[0] * remnant_flux / norm_r; M[1] = r[1] * remnant_flux / norm_r; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void south_magnetization(const xdouble &remnant_flux, +void south_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { @@ -35,11 +40,17 @@ void south_magnetization(const xdouble &remnant_flux, xdouble norm_r = sqrt(r[0] * r[0] + r[1] * r[1]); M[0] = -r[0] * remnant_flux / norm_r; M[1] = -r[1] * remnant_flux / norm_r; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void cw_magnetization(const xdouble &remnant_flux, const xdouble *x, xdouble *M) +void cw_magnetization(int vdim, + const xdouble &remnant_flux, + const xdouble *x, + xdouble *M) { xdouble r[] = {0.0, 0.0, 0.0}; r[0] = x[0]; @@ -47,11 +58,15 @@ void cw_magnetization(const xdouble &remnant_flux, const xdouble *x, xdouble *M) xdouble norm_r = sqrt(r[0] * r[0] + r[1] * r[1]); M[0] = -r[1] * remnant_flux / norm_r; M[1] = r[0] * remnant_flux / norm_r; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void ccw_magnetization(const xdouble &remnant_flux, +void ccw_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { @@ -61,34 +76,50 @@ void ccw_magnetization(const xdouble &remnant_flux, xdouble norm_r = sqrt(r[0] * r[0] + r[1] * r[1]); M[0] = r[1] * remnant_flux / norm_r; M[1] = -r[0] * remnant_flux / norm_r; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void x_axis_magnetization(const xdouble &remnant_flux, +void x_axis_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { M[0] = remnant_flux; M[1] = 0.0; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void y_axis_magnetization(const xdouble &remnant_flux, +void y_axis_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { M[0] = 0.0; M[1] = remnant_flux; - M[2] = 0.0; + if (vdim > 2) + { + M[2] = 0.0; + } } template -void z_axis_magnetization(const xdouble &remnant_flux, +void z_axis_magnetization(int vdim, + const xdouble &remnant_flux, const xdouble *x, xdouble *M) { + if (vdim < 3) + { + mfem::mfem_error("z axis magnetization only supports 3D geometry!\n"); + } M[0] = 0.0; M[1] = 0.0; M[2] = remnant_flux; @@ -98,17 +129,19 @@ void z_axis_magnetization(const xdouble &remnant_flux, /// \param[in] x - position x in space /// \param[out] M - magetic flux density at position x cause by permanent /// magnets -void northMagnetizationSource(double remnant_flux, +void northMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - north_magnetization(remnant_flux, x.GetData(), M.GetData()); + north_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// \param[in] x - position x in space of evaluation /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void northMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -123,7 +156,7 @@ void northMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - north_magnetization(remnant_flux, x_a.data(), M_a.data()); + north_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -136,17 +169,19 @@ void northMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// \param[in] x - position x in space /// \param[out] M - magetic flux density at position x cause by permanent /// magnets -void southMagnetizationSource(double remnant_flux, +void southMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - south_magnetization(remnant_flux, x.GetData(), M.GetData()); + south_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// \param[in] x - position x in space of evaluation /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void southMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -161,7 +196,7 @@ void southMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - south_magnetization(remnant_flux, x_a.data(), M_a.data()); + south_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -174,17 +209,19 @@ void southMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// \param[in] x - position x in space /// \param[out] M - magetic flux density at position x cause by permanent /// magnets -void cwMagnetizationSource(double remnant_flux, +void cwMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - cw_magnetization(remnant_flux, x.GetData(), M.GetData()); + cw_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// \param[in] x - position x in space of evaluation /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void cwMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -199,7 +236,7 @@ void cwMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - cw_magnetization(remnant_flux, x_a.data(), M_a.data()); + cw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -212,17 +249,19 @@ void cwMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// \param[in] x - position x in space /// \param[out] M - magetic flux density at position x cause by permanent /// magnets -void ccwMagnetizationSource(double remnant_flux, +void ccwMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - ccw_magnetization(remnant_flux, x.GetData(), M.GetData()); + ccw_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// \param[in] x - position x in space of evaluation /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void ccwMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -237,7 +276,7 @@ void ccwMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - ccw_magnetization(remnant_flux, x_a.data(), M_a.data()); + ccw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -249,11 +288,12 @@ void ccwMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// function defining magnetization aligned with the x axis /// \param[in] x - position x in space of evaluation /// \param[out] J - current density at position x -void xAxisMagnetizationSource(double remnant_flux, +void xAxisMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - x_axis_magnetization(remnant_flux, x.GetData(), M.GetData()); + x_axis_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// function defining magnetization aligned with the x axis @@ -261,6 +301,7 @@ void xAxisMagnetizationSource(double remnant_flux, /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void xAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -275,7 +316,7 @@ void xAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - x_axis_magnetization(remnant_flux, x_a.data(), M_a.data()); + x_axis_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -287,11 +328,12 @@ void xAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// function defining magnetization aligned with the y axis /// \param[in] x - position x in space of evaluation /// \param[out] J - current density at position x -void yAxisMagnetizationSource(double remnant_flux, +void yAxisMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - y_axis_magnetization(remnant_flux, x.GetData(), M.GetData()); + y_axis_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// function defining magnetization aligned with the x axis @@ -299,6 +341,7 @@ void yAxisMagnetizationSource(double remnant_flux, /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void yAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -313,7 +356,7 @@ void yAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - y_axis_magnetization(remnant_flux, x_a.data(), M_a.data()); + y_axis_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -325,11 +368,12 @@ void yAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, /// function defining magnetization aligned with the z axis /// \param[in] x - position x in space of evaluation /// \param[out] J - current density at position x -void zAxisMagnetizationSource(double remnant_flux, +void zAxisMagnetizationSource(int vdim, + double remnant_flux, const mfem::Vector &x, mfem::Vector &M) { - z_axis_magnetization(remnant_flux, x.GetData(), M.GetData()); + z_axis_magnetization(vdim, remnant_flux, x.GetData(), M.GetData()); } /// function defining magnetization aligned with the x axis @@ -337,6 +381,7 @@ void zAxisMagnetizationSource(double remnant_flux, /// \param[in] V_bar - /// \param[out] x_bar - V_bar^T Jacobian void zAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, + int vdim, double remnant_flux, const mfem::Vector &x, const mfem::Vector &V_bar, @@ -351,7 +396,7 @@ void zAxisMagnetizationSourceRevDiff(adept::Stack &diff_stack, diff_stack.new_recording(); // the depedent variable must be declared after the recording std::vector M_a(x.Size()); - z_axis_magnetization(remnant_flux, x_a.data(), M_a.data()); + z_axis_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); // set the independent and dependent variable diff_stack.independent(x_a.data(), x.Size()); diff_stack.dependent(M_a.data(), x.Size()); @@ -402,14 +447,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { northMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { northMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) { northMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -421,14 +468,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { southMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { southMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) { southMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -440,13 +489,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { cwMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) { + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { cwMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) + { cwMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -458,13 +510,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { ccwMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) { + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { ccwMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) + { ccwMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -476,14 +531,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { xAxisMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { xAxisMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) { xAxisMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -495,14 +552,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { yAxisMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { yAxisMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) { yAxisMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } @@ -514,14 +573,16 @@ MagnetizationCoefficient::MagnetizationCoefficient( attr, std::make_unique( vdim, - [&remnant_flux](const mfem::Vector &x, mfem::Vector &M) - { zAxisMagnetizationSource(remnant_flux, x, M); }, - [&diff_stack, &remnant_flux](const mfem::Vector &x, - const mfem::Vector &M_bar, - mfem::Vector &x_bar) + [&remnant_flux, vdim](const mfem::Vector &x, + mfem::Vector &M) + { zAxisMagnetizationSource(vdim, remnant_flux, x, M); }, + [&diff_stack, &remnant_flux, vdim]( + const mfem::Vector &x, + const mfem::Vector &M_bar, + mfem::Vector &x_bar) { zAxisMagnetizationSourceRevDiff( - diff_stack, remnant_flux, x, M_bar, x_bar); + diff_stack, vdim, remnant_flux, x, M_bar, x_bar); })); } } diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index 0afd3056..9df13335 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -86,6 +86,10 @@ MagnetostaticSolver::MagnetostaticSolver(MPI_Comm comm, nonlinear_solver = mach::constructNonlinearSolver(comm, nonlin_solver_opts, *linear_solver); nonlinear_solver->SetOperator(*spatial_res); + + mach::ParaViewLogger paraview("magnetostatic", &mesh()); + paraview.registerField("state", fields.at("state").gridFunc()); + addLogger(std::move(paraview), {}); } void MagnetostaticSolver::addOutput(const std::string &fun, diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index cdcb49f2..3f7fe731 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -167,10 +167,23 @@ MagnetostaticResidual::MagnetostaticResidual( res.addDomainIntegrator(new NonlinearDiffusionIntegrator(nu)); MachLinearForm linear_form(fes, fields); - current_coeff = std::make_unique( - diff_stack, options["current"]); - linear_form.addDomainIntegrator( - new mfem::DomainLFIntegrator(*current_coeff)); + if (options.contains("current")) + { + current_coeff = std::make_unique( + diff_stack, options["current"]); + linear_form.addDomainIntegrator( + new mfem::DomainLFIntegrator(*current_coeff)); + } + if (options.contains("magnets")) + { + mag_coeff = std::make_unique( + diff_stack, options["magnets"], materials, 2); + nuM = std::make_unique( + nu, *mag_coeff); + + linear_form.addDomainIntegrator( + new MagnetizationSource2DIntegrator(*nuM, 1.0)); + } load = std::make_unique(std::move(linear_form)); } diff --git a/src/physics/electromagnetics/magnetostatic_residual.hpp b/src/physics/electromagnetics/magnetostatic_residual.hpp index 8f59cff4..571fa56b 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.hpp +++ b/src/physics/electromagnetics/magnetostatic_residual.hpp @@ -85,6 +85,8 @@ class MagnetostaticResidual final // MagnetostaticLoad load; std::unique_ptr load; std::unique_ptr current_coeff; + std::unique_ptr mag_coeff; + std::unique_ptr nuM; /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index 62f3c1c5..2d4d1bc8 100644 --- a/src/utils/l2_transfer_operator.cpp +++ b/src/utils/l2_transfer_operator.cpp @@ -1,4 +1,4 @@ -#include +#include #include "mach_input.hpp" @@ -784,6 +784,65 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation } }; +class CurlMagnitudeOperator2D : public mach::L2TransferOperation +{ +public: + void apply(const mfem::FiniteElement &state_fe, + const mfem::FiniteElement &output_fe, + mfem::ElementTransformation &trans, + const mfem::Vector &el_state, + mfem::Vector &el_output) const override + { + int state_dof = state_fe.GetDof(); + + int el_dim = state_fe.GetDim(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + + mfem::DenseMatrix dshape(state_dof, el_dim); + mfem::DenseMatrix dshapedxt(state_dof, space_dim); + mfem::DenseMatrix curlshape(state_dof, curl_dim); + + double curl_vec_buffer[3]; + mfem::Vector curl_vec(curl_vec_buffer, curl_dim); + + const auto &ir = output_fe.GetNodes(); + for (int i = 0; i < ir.GetNPoints(); ++i) + { + const auto &ip = ir.IntPoint(i); + trans.SetIntPoint(&ip); + + state_fe.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + mfem::DenseMatrix tmp(curlshape.GetData(), space_dim * state_dof, 1); + dshapedxt.GradToCurl(tmp); + + curlshape.MultTranspose(el_state, curl_vec); + + const double curl_vec_norm = curl_vec.Norml2(); + const double trans_weight = trans.Weight(); + const double curl_mag = curl_vec_norm / trans_weight; + el_output(i) = curl_mag; + } + } + + void apply_state_bar(const mfem::FiniteElement &state_fe, + const mfem::FiniteElement &output_fe, + mfem::ElementTransformation &trans, + const mfem::Vector &el_output_adj, + const mfem::Vector &el_state, + mfem::Vector &el_state_bar) const override + { } + void apply_mesh_coords_bar(const mfem::FiniteElement &state_fe, + const mfem::FiniteElement &output_fe, + mfem::ElementTransformation &trans, + const mfem::Vector &el_output_adj, + const mfem::Vector &el_state, + mfem::Vector &mesh_coords_bar) const override + { } +}; + } // anonymous namespace namespace mach @@ -823,7 +882,17 @@ L2CurlMagnitudeProjection::L2CurlMagnitudeProjection( : L2TransferOperator(state, mesh_coords, output, - std::make_unique()) + [&state]() -> std::unique_ptr + { + if (state.mesh().SpaceDimension() == 3) + { + return std::make_unique(); + } + else + { + return std::make_unique(); + } + }()) { } void L2TransferOperator::apply(const MachInputs &inputs, mfem::Vector &out_vec) diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index 0c1ccf6e..021fa507 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -25,6 +25,7 @@ set(REGRESSION_TEST_SRCS test_meshmovement_annulus test_ac_loss test_magnetostatic_box_new + test_2d_magnet_in_box ) create_tests("${REGRESSION_TEST_SRCS}" regression_data.cpp) From d822a3f43c6d68db76799ac1b4859601114aa1cb Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 25 Jul 2022 11:50:14 -0600 Subject: [PATCH 10/72] working on 2D torque, making sure it works as expected --- mach/mach_output.py | 6 ++- mach/mach_state.py | 6 ++- .../electromagnetics/electromag_integ.cpp | 53 ++++++++++--------- .../electromagnetics/electromag_outputs.cpp | 32 +++++++---- .../electromagnetics/electromag_outputs.hpp | 14 ++++- .../magnetostatic_residual.cpp | 1 + src/physics/pde_solver.cpp | 6 +++ 7 files changed, 79 insertions(+), 39 deletions(-) diff --git a/mach/mach_output.py b/mach/mach_output.py index 1c04d6cb..ef446804 100644 --- a/mach/mach_output.py +++ b/mach/mach_output.py @@ -46,9 +46,11 @@ def setup(self): self.vectors["state"] = np.empty(0) elif input == "mesh_coords": + mesh_size = solver.getFieldSize(input) self.add_input("mesh_coords", - distributed=True, - shape_by_conn=True, + # distributed=True, + shape=mesh_size, + # shape_by_conn=True, desc="volume mesh node coordinates", tags=["mphys_coordinates"]) self.vectors["mesh_coords"] = np.empty(0) diff --git a/mach/mach_state.py b/mach/mach_state.py index b16f0375..378048eb 100644 --- a/mach/mach_state.py +++ b/mach/mach_state.py @@ -58,9 +58,11 @@ def setup(self): ext_fields = "external-fields" in solver_options for input in self.options["depends"]: if input == "mesh_coords": + mesh_size = solver.getFieldSize(input) self.add_input("mesh_coords", - distributed=True, - shape_by_conn=True, + # distributed=True, + shape=mesh_size, + # shape_by_conn=True, desc="volume mesh node coordinates", tags=["mphys_coordinates"]) self.vectors["mesh_coords"] = np.empty(0) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 4b8e0b1e..7ff0569f 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -13,7 +13,7 @@ double calcMagneticEnergy(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -34,7 +34,7 @@ double calcMagneticEnergyDot(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -63,7 +63,7 @@ double calcMagneticEnergyDoubleDot(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); /// compute int_0^{B} \nuB dB double d2endB2 = 0.0; @@ -3323,23 +3323,21 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, /// number of degrees of freedom int ndof = el.GetDof(); int dim = el.GetDim(); - - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = (dim == 3) ? 3 : space_dim; #ifdef MFEM_THREAD_SAFE DenseMatrix dshape(v_el.GetDof(), v_el.GetDim()); - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); + DenseMatrix curlshape(ndof, curl_dim), curlshape_dFt(ndof, curl_dim); DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); - Vector b_vec(dimc), b_hat(dimc); + Vector b_vec(curl_dim), b_hat(curl_dim); #else dshape.SetSize(v_el.GetDof(), v_el.GetDim()); - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); - b_vec.SetSize(dimc); - b_hat.SetSize(dimc); + b_vec.SetSize(curl_dim); + b_hat.SetSize(curl_dim); #endif // cast the ElementTransformation @@ -3369,37 +3367,42 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + const double w = ip.weight * trans_weight; if (dim == 3) { el.CalcCurlShape(ip, curlshape); MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } - else + else // Dealing with scalar H1 field representing Az { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + // el.CalcCurlShape(ip, curlshape_dFt); } b_vec = 0.0; curlshape_dFt.AddMultTranspose(elfun, b_vec); const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans.Weight(); + const double b_mag = b_vec_norm / trans_weight; /// the following computes `\partial (||B||/|J|) / \partial J` - DenseMatrix dBmdJ(dimc); + double dBmdJ_buffer[9]; + DenseMatrix dBmdJ(dBmdJ_buffer, curl_dim, curl_dim); dBmdJ = 0.0; b_hat = 0.0; curlshape.AddMultTranspose(elfun, b_hat); - DenseMatrix BB_hatT(dimc); + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); auto inv_jac_transposed = trans.InverseJacobian(); inv_jac_transposed.Transpose(); - Add(1.0 / (trans.Weight() * b_vec_norm), + Add(1.0 / (trans_weight * b_vec_norm), BB_hatT, - -b_vec_norm / trans.Weight(), + -b_vec_norm / trans_weight, inv_jac_transposed, dBmdJ); @@ -3410,7 +3413,7 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, double dBds = 0.0; for (int j = 0; j < v_el.GetDof(); ++j) { - for (int d = 0; d < dimc; ++d) + for (int d = 0; d < curl_dim; ++d) { dBds += dBdX(d, j) * dXds(j, d); } @@ -3419,8 +3422,10 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, auto force = dBds * energy_dot; v_el.CalcDShape(ip, dshape); - DenseMatrix JinvdJds(3); - DenseMatrix dJds(3); + double JinvdJds_buffer[9]; + DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); + double dJds_buffer[9]; + DenseMatrix dJds(dJds_buffer, space_dim, space_dim); MultAtB(dXds, dshape, dJds); Mult(trans.InverseJacobian(), dJds, JinvdJds); double JinvdJdsTrace = JinvdJds.Trace(); diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 342b696f..aadc5b43 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -15,8 +15,10 @@ void setOptions(ForceFunctional &output, const nlohmann::json &options) { auto &&attrs = options["attributes"].get>(); auto &&axis = options["axis"].get>(); + + auto space_dim = output.fields.at("vforce").mesh().SpaceDimension(); mfem::VectorConstantCoefficient axis_vector( - mfem::Vector(&axis[0], axis.size())); + mfem::Vector(&axis[0], space_dim)); auto &v = output.fields.at("vforce").gridFunc(); v = 0.0; @@ -33,18 +35,30 @@ void setOptions(TorqueFunctional &output, const nlohmann::json &options) auto &&about = options["about"].get>(); mfem::Vector axis_vector(&axis[0], axis.size()); axis_vector /= axis_vector.Norml2(); - mfem::Vector about_vector(&about[0], axis.size()); + + auto space_dim = output.fields.at("vtorque").mesh().SpaceDimension(); + mfem::Vector about_vector(&about[0], space_dim); double r_data[3]; - mfem::Vector r(r_data, axis.size()); + mfem::Vector r(r_data, space_dim); + mfem::VectorFunctionCoefficient v_vector( - 3, - [&axis_vector, &about_vector, &r](const mfem::Vector &x, mfem::Vector &v) + space_dim, + [&axis_vector, &about_vector, &r, space_dim](const mfem::Vector &x, + mfem::Vector &v) { subtract(x, about_vector, r); - // r /= r.Norml2(); - v(0) = axis_vector(1) * r(2) - axis_vector(2) * r(1); - v(1) = axis_vector(2) * r(0) - axis_vector(0) * r(2); - v(2) = axis_vector(0) * r(1) - axis_vector(1) * r(0); + if (space_dim == 3) + { + // r /= r.Norml2(); + v(0) = axis_vector(1) * r(2) - axis_vector(2) * r(1); + v(1) = axis_vector(2) * r(0) - axis_vector(0) * r(2); + v(2) = axis_vector(0) * r(1) - axis_vector(1) * r(0); + } + else + { + v(0) = -axis_vector(2) * r(1); + v(1) = axis_vector(2) * r(0); + } // if (v.Norml2() > 1e-12) // v /= v.Norml2(); }); diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index eb5c3aad..4616d6a7 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -149,8 +149,18 @@ class TorqueFunctional final setOptions(*this, options); auto &&attrs = options["attributes"].get>(); - output.addOutputDomainIntegrator( - new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); + if (options.contains("air_attributes")) + { + auto &&air_attrs = options["air_attributes"].get>(); + output.addOutputDomainIntegrator( + new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs), + air_attrs); + } + else + { + output.addOutputDomainIntegrator( + new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); + } } private: diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 3f7fe731..0428662a 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -156,6 +156,7 @@ MagnetostaticResidual::MagnetostaticResidual( { auto *mesh = fes.GetParMesh(); auto space_dim = mesh->SpaceDimension(); + // auto space_dim = mesh->Dimension(); if (space_dim == 3) { res.addDomainIntegrator(new CurlCurlNLFIntegrator(nu)); diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index cccda0f4..93960160 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -1,3 +1,4 @@ +#include #include "finite_element_dual.hpp" #include "mfem.hpp" @@ -204,6 +205,11 @@ MachMesh constructMesh(MPI_Comm comm, { throw MachException("Unrecognized mesh file extension!\n"); } + // auto *nodes = mesh.mesh->GetNodes(); + // if (nodes == nullptr) + // { + // mesh.mesh->SetCurvature(1, false, 3, mfem::Ordering::byVDIM); + // } mesh.mesh->EnsureNodes(); if (!keep_boundaries) From aa14d7856a6cd3e8bffeeebeb44fc9f9ae183b53 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 25 Jul 2022 13:59:11 -0600 Subject: [PATCH 11/72] check if cmake policies exist before setting them --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8876c180..f7ec7a72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,9 @@ endif (MFEM_USE_PUMI) # find the MPI compilers find_package(MPI REQUIRED) -cmake_policy(SET CMP0077 NEW) +if (POLICY CMP0077) + cmake_policy(SET CMP0077 NEW) +endif (POLICY CMP0077) set(TINYSPLINE_ENABLE_CXX ON) set(TINYSPLINE_INSTALL ON) include(FetchContent) @@ -281,7 +283,9 @@ if (BUILD_PYTHON_WRAPPER) include(FetchContent) - cmake_policy(SET CMP0127 NEW) + if (POLICY CMP0127) + cmake_policy(SET CMP0127 NEW) + endif (POLICY CMP0127) FetchContent_Declare(pybind11 GIT_REPOSITORY "https://github.com/pybind/pybind11" GIT_TAG v2.7.1 From 07ef8bd8060abb1f52e9470316043a12ba788f4a Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 25 Jul 2022 14:00:55 -0600 Subject: [PATCH 12/72] remove 2d magnet regression test --- test/regression/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index 021fa507..376d42dc 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -25,7 +25,7 @@ set(REGRESSION_TEST_SRCS test_meshmovement_annulus test_ac_loss test_magnetostatic_box_new - test_2d_magnet_in_box + # test_2d_magnet_in_box ) create_tests("${REGRESSION_TEST_SRCS}" regression_data.cpp) From 1cfc0454c2647f36a8c120d981a84641d19fd3cc Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 27 Jul 2022 13:47:06 -0600 Subject: [PATCH 13/72] added new integrator to calculate force/torques that works for 2D and 3D. Integrator approach follows a more straightforward AD approach than the previous integrator, using hand written AD of the MagneticEnergy integrator --- .../electromagnetics/electromag_integ.cpp | 236 +++++++++++++++--- .../electromagnetics/electromag_integ.hpp | 41 +++ test/unit/test_electromag_integ.cpp | 65 +++++ 3 files changed, 314 insertions(+), 28 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 7ff0569f..9d2a1014 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -1465,19 +1465,22 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, /// number of degrees of freedom int ndof = el.GetDof(); int dim = el.GetDim(); - - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - b_vec.SetSize(dimc); + // DenseMatrix dshape; + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + Vector b_vec; #endif + // dshape.SetSize(ndof, space_dim); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + b_vec.SetSize(curl_dim); + + // double b_vec_buffer[3]; + // Vector b_vec(b_vec_buffer, curl_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -1513,7 +1516,8 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, } else { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } b_vec = 0.0; @@ -3298,6 +3302,172 @@ double HybridACLossFunctionalIntegrator::GetElementEnergy( return fun; } +double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + if (attrs.count(trans.Attribute) == 1) + { + return 0.0; + } + /// get the proper element, transformation, and v vector +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector vfun; +#endif + int element = trans.ElementNo; + const auto &v_el = *v.FESpace()->GetFE(element); + v.FESpace()->GetElementVDofs(element, vdofs); + v.GetSubVector(vdofs, vfun); + DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); + if (vfun.Normlinf() < 1e-14) + { + return 0.0; + } + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape(v_el.GetDof(), v_el.GetDim()); + DenseMatrix curlshape(ndof, curl_dim), curlshape_dFt(ndof, curl_dim); + DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); + Vector b_vec(curl_dim), b_hat(curl_dim); +#else + dshape.SetSize(v_el.GetDof(), v_el.GetDim()); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + // dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); + b_vec.SetSize(curl_dim); + b_hat.SetSize(curl_dim); +#endif + + Vector curlshape_dFt_bar_buffer(curl_dim * ndof); + DenseMatrix PointMat_bar(space_dim, v_el.GetDof()); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + const double trans_weight = trans.Weight(); + /// holds quadrature weight + double w = ip.weight * trans_weight; + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else // Dealing with scalar H1 field representing Az + { + /// Not exactly the curl matrix, but since we just want the magnitude + /// of the curl its okay + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // fun += energy * w; + double fun_bar = 1.0; + + double energy_bar = 0.0; + double w_bar = 0.0; + energy_bar += fun_bar * w; + w_bar += fun_bar * energy; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + b_mag_bar += energy_bar * energy_dot; + + double b_vec_norm_bar = 0.0; + double trans_weight_bar = 0.0; + /// const double b_mag = b_vec_norm / trans_weight; + b_vec_norm_bar += b_mag_bar / trans_weight; + trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); + b_vec_bar = 0.0; + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // transposed dimensions of curlshape_dFt so I don't have to transpose + // jac_bar later + DenseMatrix curlshape_dFt_bar(curlshape_dFt_bar_buffer.GetData(), curl_dim, ndof); + MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9]; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + DenseMatrix curlshape_dFt_bar(curlshape_dFt_bar_buffer.GetData(), ndof, curl_dim); + MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9]; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + adj_bar = 0.0; + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int d = 0; d < space_dim; ++d) + { + fun -= dXds(j, d) * PointMat_bar(d, j); + } + } + } + return fun; +} + double ForceIntegrator::GetElementEnergy(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun) @@ -3324,7 +3494,7 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, int ndof = el.GetDof(); int dim = el.GetDim(); int space_dim = trans.GetSpaceDim(); - int curl_dim = (dim == 3) ? 3 : space_dim; + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE DenseMatrix dshape(v_el.GetDof(), v_el.GetDim()); @@ -3369,7 +3539,7 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, const double trans_weight = trans.Weight(); /// holds quadrature weight - const double w = ip.weight * trans_weight; + double w = ip.weight * trans_weight; if (dim == 3) { el.CalcCurlShape(ip, curlshape); @@ -3377,9 +3547,10 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, } else // Dealing with scalar H1 field representing Az { + /// Not exactly the curl matrix, but since we just want the magnitude + /// of the curl its okay el.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - // el.CalcCurlShape(ip, curlshape_dFt); } b_vec = 0.0; @@ -3391,20 +3562,29 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, double dBmdJ_buffer[9]; DenseMatrix dBmdJ(dBmdJ_buffer, curl_dim, curl_dim); dBmdJ = 0.0; - b_hat = 0.0; - curlshape.AddMultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; - DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); - MultVWt(b_vec, b_hat, BB_hatT); - - auto inv_jac_transposed = trans.InverseJacobian(); - inv_jac_transposed.Transpose(); - - Add(1.0 / (trans_weight * b_vec_norm), - BB_hatT, - -b_vec_norm / trans_weight, - inv_jac_transposed, - dBmdJ); + if (dim == 3) + { + b_hat = 0.0; + curlshape.AddMultTranspose(elfun, b_hat); + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + auto inv_jac_transposed = trans.InverseJacobian(); + inv_jac_transposed.Transpose(); + + Add(1.0 / (trans_weight * b_vec_norm), + BB_hatT, + -b_vec_norm / trans_weight, + inv_jac_transposed, + dBmdJ); + } + else + { + b_hat = 0.0; + trans.InverseJacobian().Mult(b_vec, b_hat); + AddMult_a_VWt(-1 / b_vec_norm, b_vec, b_hat, dBmdJ); + } /// and then contracts with \partial J / \partial X dBdX = 0.0; diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 45315977..76b1b551 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1155,6 +1155,47 @@ class HybridACLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator #endif }; +class ForceIntegrator2 : public mfem::NonlinearFormIntegrator +{ +public: + ForceIntegrator2(StateCoefficient &nu, mfem::GridFunction &v) : nu(nu), v(v) + { } + + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] nu - model describing reluctivity + /// \param[in] v - the grid function containing virtual displacements for + /// each mesh node + /// \param[in] attrs - the regions the force is acting on + ForceIntegrator2(StateCoefficient &nu, + mfem::GridFunction &v, + std::unordered_set attrs) + : nu(nu), v(v), attrs(std::move(attrs)) + { } + + /// \brief - Compute element contribution to global force/torque + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \returns the element contribution to global force/torque + double GetElementEnergy(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) override; + +private: + /// material dependent model describing reluctivity + StateCoefficient ν + /// grid function containing virtual displacements for each mesh node + mfem::GridFunction &v; + /// set of attributes the force is acting on + std::unordered_set attrs; +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshape, curlshape, curlshape_dFt, dBdX; + mfem::Vector b_vec, b_hat; + mfem::Array vdofs; + mfem::Vector vfun; +#endif +}; + /// Functional integrator to compute forces/torques based on the virtual work /// method class ForceIntegrator : public mfem::NonlinearFormIntegrator diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index f3a7ae38..906eb474 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -1743,6 +1743,71 @@ TEST_CASE("calcMagneticEnergyDoubleDot") } } +TEST_CASE("ForceIntegrator2::GetElementEnergy") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + /// construct elements + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + FunctionCoefficient apert(randState); + a.ProjectCoefficient(apert); + // a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator2(nu, v)); + + auto force = functional.GetEnergy(a); + + NonlinearForm energy(&fes); + energy.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + add(x_nodes, -delta, v, x_nodes); + auto dWds = -energy.GetEnergy(a); + add(x_nodes, 2*delta, v, x_nodes); + dWds += energy.GetEnergy(a); + dWds /= 2*delta; + + // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; + REQUIRE(force == Approx(-dWds)); + } + } +} + TEST_CASE("ForceIntegrator::GetElementEnergy") { using namespace mfem; From 59acdc301e0b1250d95ad6be282a75054dff332c Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:34:57 -0600 Subject: [PATCH 14/72] differentiate magnetic energy functional for 2D case wrt state and mesh and test both --- .../electromagnetics/electromag_integ.cpp | 228 ++++++++++++++---- test/unit/test_electromag_integ.cpp | 182 +++++++++++++- 2 files changed, 357 insertions(+), 53 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 9d2a1014..a77432ce 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -1,3 +1,4 @@ +#include #include "coefficient.hpp" #include "electromag_integ.hpp" #include "mach_input.hpp" @@ -1469,18 +1470,14 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - // DenseMatrix dshape; DenseMatrix curlshape; DenseMatrix curlshape_dFt; - Vector b_vec; #endif - // dshape.SetSize(ndof, space_dim); curlshape.SetSize(ndof, curl_dim); curlshape_dFt.SetSize(ndof, curl_dim); - b_vec.SetSize(curl_dim); - // double b_vec_buffer[3]; - // Vector b_vec(b_vec_buffer, curl_dim); + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -1507,7 +1504,8 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, trans.SetIntPoint(&ip); /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; if (dim == 3) { @@ -1523,7 +1521,7 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, b_vec = 0.0; curlshape_dFt.AddMultTranspose(elfun, b_vec); const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans.Weight(); + const double b_mag = b_vec_norm / trans_weight; const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); fun += energy * w; @@ -1535,24 +1533,28 @@ void MagneticEnergyIntegrator::AssembleElementVector( const FiniteElement &el, ElementTransformation &trans, const Vector &elfun, - Vector &elvect) + Vector &elfun_bar) { /// number of degrees of freedom int ndof = el.GetDof(); int dim = el.GetDim(); - - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - b_vec.SetSize(dimc); + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; #endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + + // Vector curlshape_dFt_bar_buffer(curl_dim * ndof); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -1572,15 +1574,16 @@ void MagneticEnergyIntegrator::AssembleElementVector( ir = &IntRules.Get(el.GetGeomType(), order); } - elvect.SetSize(ndof); - elvect = 0.0; + elfun_bar.SetSize(ndof); + elfun_bar = 0.0; for (int i = 0; i < ir->GetNPoints(); i++) { const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); /// holds quadrature weight - const double w = ip.weight / trans.Weight(); + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; if (dim == 3) { @@ -1589,17 +1592,42 @@ void MagneticEnergyIntegrator::AssembleElementVector( } else { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } b_vec = 0.0; curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_mag = b_vec.Norml2() / trans.Weight(); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // fun += energy * w; + double fun_bar = 1.0; + + double energy_bar = 0.0; + // double w_bar = 0.0; + energy_bar += fun_bar * w; + // w_bar += fun_bar * energy; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + b_mag_bar += energy_bar * energy_dot; - b_vec *= energy_dot * w / b_mag; - curlshape_dFt.AddMult(b_vec, elvect); + double b_vec_norm_bar = 0.0; + // double trans_weight_bar = 0.0; + /// const double b_mag = b_vec_norm / trans_weight; + b_vec_norm_bar += b_mag_bar / trans_weight; + // trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + b_vec_bar = 0.0; + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + // curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt.AddMult(b_vec_bar, elfun_bar); } } @@ -1608,8 +1636,6 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( ElementTransformation &mesh_trans, Vector &mesh_coords_bar) { - auto &nu = integ.nu; - /// get the proper element, transformation, and state vector #ifdef MFEM_THREAD_SAFE Array vdofs; @@ -1622,9 +1648,8 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( const int ndof = mesh_el.GetDof(); const int el_ndof = el.GetDof(); const int dim = el.GetDim(); - const int dimc = (dim == 3) ? 3 : 1; - mesh_coords_bar.SetSize(ndof * dimc); - mesh_coords_bar = 0.0; + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); state.GetSubVector(vdofs, elfun); @@ -1633,26 +1658,29 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( dof_tr->InvTransformPrimal(elfun); } + auto &nu = integ.nu; + #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(el_ndof, dimc); - DenseMatrix curlshape_dFt(el_ndof, dimc); - Vector b_vec(dimc); - DenseMatrix curlshape_dFt_bar( - dimc, el_ndof); // transposed dimensions of curlshape_dFt so I don't - // have to transpose J later - DenseMatrix PointMat_bar(dimc, ndof); + DenseMatrix curlshape(el_ndof, curl_dim); + DenseMatrix curlshape_dFt(el_ndof, curl_dim); + DenseMatrix curlshape_dFt_bar(curl_dim, el_ndof); + DenseMatrix PointMat_bar(curl_dim, ndof); #else auto &curlshape = integ.curlshape; auto &curlshape_dFt = integ.curlshape_dFt; - auto &b_vec = integ.b_vec; - curlshape.SetSize(el_ndof, dimc); - curlshape_dFt.SetSize(el_ndof, dimc); - b_vec.SetSize(dimc); - curlshape_dFt_bar.SetSize( - dimc, el_ndof); // transposed dimensions of curlshape_dFt so I don't - // have to transpose J later - PointMat_bar.SetSize(dimc, ndof); #endif + curlshape.SetSize(el_ndof, curl_dim); + curlshape_dFt.SetSize(el_ndof, curl_dim); + curlshape_dFt_bar.SetSize(curl_dim, el_ndof); + PointMat_bar.SetSize(curl_dim, ndof); + + // Vector curlshape_dFt_bar_buffer(curl_dim * ndof); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation auto &isotrans = dynamic_cast(trans); @@ -1675,11 +1703,108 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( ir = &IntRules.Get(el.GetGeomType(), order); } + mesh_coords_bar.SetSize(ndof * space_dim); + mesh_coords_bar = 0.0; for (int i = 0; i < ir->GetNPoints(); i++) { const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); + /// holds quadrature weight + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + + // fun += energy * w; + double fun_bar = 1.0; + + double energy_bar = 0.0; + double w_bar = 0.0; + energy_bar += fun_bar * w; + w_bar += fun_bar * energy; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + b_mag_bar += energy_bar * energy_dot; + + double b_vec_norm_bar = 0.0; + double trans_weight_bar = 0.0; + /// const double b_mag = b_vec_norm / trans_weight; + b_vec_norm_bar += b_mag_bar / trans_weight; + trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + b_vec_bar = 0.0; + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // transposed dimensions of curlshape_dFt so I don't have to transpose + // jac_bar later + // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), curl_dim, el_ndof); + // DenseMatrix curlshape_dFt_bar_(curl_dim, el_ndof); + curlshape_dFt_bar.SetSize(curl_dim, el_ndof); + MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9]; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), el_ndof, curl_dim); + // DenseMatrix curlshape_dFt_bar_(el_ndof, curl_dim); + curlshape_dFt_bar.SetSize(el_ndof, curl_dim); + MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9]; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + // adj_bar = 0.0; + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + // double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); + } + } + + /* /// holds quadrature weight const double w = ip.weight * trans.Weight(); if (dim == 3) @@ -1713,13 +1838,15 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); b_mag_bar += energy_bar * energy_dot; + std::cout << "b_mag_bar: " << b_mag_bar << "\n"; + double b_vec_norm_bar = 0.0; double trans_weight_bar = 0.0; /// const double b_mag = b_vec_norm / trans.Weight(); b_vec_norm_bar += b_mag_bar / trans.Weight(); trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans.Weight(), 2); - Vector b_vec_bar(dimc); + Vector b_vec_bar(curl_dim); b_vec_bar = 0.0; /// const double b_vec_norm = b_vec.Norml2(); add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); @@ -1728,7 +1855,7 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - DenseMatrix jac_bar(dimc); + DenseMatrix jac_bar(curl_dim); jac_bar = 0.0; AddMult(curlshape_dFt_bar, curlshape, jac_bar); @@ -1744,11 +1871,12 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( // code to insert PointMat_bar into mesh_coords_bar; for (int j = 0; j < ndof; ++j) { - for (int d = 0; d < dimc; ++d) + for (int d = 0; d < curl_dim; ++d) { mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); } } + */ } } @@ -3385,7 +3513,7 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, else // Dealing with scalar H1 field representing Az { /// Not exactly the curl matrix, but since we just want the magnitude - /// of the curl its okay + /// of the curl it's okay el.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 906eb474..7be04051 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -827,6 +827,56 @@ TEST_CASE("VectorFEDomainLFMeshSensInteg::AssembleRHSElementVect" // } // } +TEST_CASE("MagneticEnergyIntegrator::GetEnergy - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + auto nonlin_energy = [](double B) { return 1.0/3.0 * (sqrt(B+1) * (B-2) + 2); }; + + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient a_func([](const mfem::Vector &x){ + return 1.5*x(1) - 0.5*x(0); + }); + a.ProjectCoefficient(a_func); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + const double fun = functional.GetEnergy(a); + const double b_mag = sqrt(pow(1.5, 2) + pow(0.5, 2)); + const double energy = nonlin_energy(b_mag); + const double area = 1.0; + // std::cout << "fun: " << fun << " energy * area: " << energy*area << "\n"; + REQUIRE(fun == Approx(energy * area)); + } + } +} + TEST_CASE("MagneticEnergyIntegrator::GetEnergy") { using namespace mfem; @@ -855,8 +905,6 @@ TEST_CASE("MagneticEnergyIntegrator::GetEnergy") std::unique_ptr fes(new FiniteElementSpace( mesh.get(), fec.get())); - - // initialize state; here we randomly perturb a constant state GridFunction A(fes.get()); VectorFunctionCoefficient pert(3, [](const Vector &x, Vector &a) { @@ -883,6 +931,64 @@ TEST_CASE("MagneticEnergyIntegrator::GetEnergy") } } +TEST_CASE("MagneticEnergyIntegrator::AssembleElementVector - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + TEST_CASE("MagneticEnergyIntegrator::AssembleElementVector") { using namespace mfem; @@ -938,6 +1044,77 @@ TEST_CASE("MagneticEnergyIntegrator::AssembleElementVector") } } +TEST_CASE("MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::MagneticEnergyIntegrator(nu); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::MagneticEnergyIntegratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect") { using namespace mfem; @@ -1762,7 +1939,6 @@ TEST_CASE("ForceIntegrator2::GetElementEnergy") NonLinearCoefficient nu; // LinearCoefficient nu; - /// construct elements for (int p = 1; p <= 4; ++p) { DYNAMIC_SECTION("...for degree p = " << p) From 3fcea8afafe2420a913aa95d2584331d168a21a5 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:38:28 -0600 Subject: [PATCH 15/72] updated torque outut to use new ForceIntegrator2 that works for 2D and 3D. Once it is differentiated, I will rename it to ForceIntegrator and remove the old one. Set it up so that magnetic energy could be computed on a per attribute basis, so you could calculate just the energy in the airgap for example. Now only add the current density integrator to the winding attributes so that it hopefully runs more efficiently --- src/common/material_library.cpp | 2 +- .../electromagnetics/electromag_outputs.cpp | 1 + .../electromagnetics/electromag_outputs.hpp | 6 +++-- .../electromagnetics/magnetostatic.cpp | 11 +++++++- .../magnetostatic_residual.cpp | 25 +++++++++++++++++-- src/physics/pde_solver.cpp | 2 +- 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/common/material_library.cpp b/src/common/material_library.cpp index b8b5f386..cdb0dea8 100644 --- a/src/common/material_library.cpp +++ b/src/common/material_library.cpp @@ -118,7 +118,7 @@ const nlohmann::json material_library{ 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, 3.97887360e+04}}, - {"mu_r", 750}, + {"mu_r", 500}, {"rho", 8110.0}, {"cv", 0.420}, {"kappa", 20}, diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index aadc5b43..ca7bb2e3 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1,5 +1,6 @@ #include +#include "data_logging.hpp" #include "electromag_integ.hpp" #include "mach_integrator.hpp" #include "mfem.hpp" diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 4616d6a7..6043bfd6 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -153,13 +153,15 @@ class TorqueFunctional final { auto &&air_attrs = options["air_attributes"].get>(); output.addOutputDomainIntegrator( - new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs), + new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs), + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs), air_attrs); } else { output.addOutputDomainIntegrator( - new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); + new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs)); + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); } } diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index 9df13335..4dfec56c 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -99,7 +99,16 @@ void MagnetostaticSolver::addOutput(const std::string &fun, if (fun.rfind("energy", 0) == 0) { FunctionalOutput out(fes(), fields); - out.addOutputDomainIntegrator(new MagneticEnergyIntegrator(nu)); + if (options.contains("attributes")) + { + auto attributes = options["attributes"].get>(); + out.addOutputDomainIntegrator(new MagneticEnergyIntegrator(nu), + attributes); + } + else + { + out.addOutputDomainIntegrator(new MagneticEnergyIntegrator(nu)); + } outputs.emplace(fun, std::move(out)); } else if (fun.rfind("force", 0) == 0) diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 0428662a..62a4d157 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -4,6 +4,7 @@ #include "adept.h" #include "electromag_integ.hpp" #include "mach_linearform.hpp" +#include "mach_load.hpp" #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -36,7 +37,21 @@ std::unique_ptr constructPreconditioner( return nullptr; } -} // namespace +std::vector getCurrentAttributes(const nlohmann::json &options) +{ + std::vector attributes; + for (const auto &group : options["current"]) + { + for (const auto &source : group) + { + auto attrs = source.get>(); + attributes.insert(attributes.end(), attrs.begin(), attrs.end()); + } + } + return attributes; +} + +} // anonymous namespace namespace mach { @@ -49,6 +64,10 @@ void setInputs(MagnetostaticResidual &residual, const mach::MachInputs &inputs) { setInputs(residual.res, inputs); setInputs(*residual.load, inputs); + if (residual.current_coeff) + { + setInputs(*residual.current_coeff, inputs); + } } void setOptions(MagnetostaticResidual &residual, const nlohmann::json &options) @@ -172,8 +191,10 @@ MagnetostaticResidual::MagnetostaticResidual( { current_coeff = std::make_unique( diff_stack, options["current"]); + + auto current_attrs = getCurrentAttributes(options); linear_form.addDomainIntegrator( - new mfem::DomainLFIntegrator(*current_coeff)); + new mfem::DomainLFIntegrator(*current_coeff), current_attrs); } if (options.contains("magnets")) { diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index 93960160..b607686f 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -150,7 +150,7 @@ MachMesh::~MachMesh() /// If we started PCU and we're the last one using it, close it if (!PCU_previously_initialized && pumi_mesh_count == 0) { -#ifdef HAVE_EGADS +#ifdef MFEM_USE_EGADS gmi_egads_stop(); #endif #ifdef HAVE_SIMMETRIX From d4a800c476207da4915484a2b57315a790699a6a Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:38:47 -0600 Subject: [PATCH 16/72] make format --- .../electromagnetics/electromag_integ.cpp | 16 ++++++++++------ .../electromagnetics/electromag_outputs.hpp | 5 +++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index a77432ce..ddc8e37a 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -1761,8 +1761,9 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( /// curlshape_dFt.AddMultTranspose(elfun, b_vec); // transposed dimensions of curlshape_dFt so I don't have to transpose // jac_bar later - // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), curl_dim, el_ndof); - // DenseMatrix curlshape_dFt_bar_(curl_dim, el_ndof); + // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), + // curl_dim, el_ndof); DenseMatrix curlshape_dFt_bar_(curl_dim, + // el_ndof); curlshape_dFt_bar.SetSize(curl_dim, el_ndof); MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); @@ -1776,8 +1777,9 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( else // Dealing with scalar H1 field representing Az { /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), el_ndof, curl_dim); - // DenseMatrix curlshape_dFt_bar_(el_ndof, curl_dim); + // DenseMatrix curlshape_dFt_bar_(curlshape_dFt_bar_buffer.GetData(), + // el_ndof, curl_dim); DenseMatrix curlshape_dFt_bar_(el_ndof, + // curl_dim); curlshape_dFt_bar.SetSize(el_ndof, curl_dim); MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); @@ -3555,7 +3557,8 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, /// curlshape_dFt.AddMultTranspose(elfun, b_vec); // transposed dimensions of curlshape_dFt so I don't have to transpose // jac_bar later - DenseMatrix curlshape_dFt_bar(curlshape_dFt_bar_buffer.GetData(), curl_dim, ndof); + DenseMatrix curlshape_dFt_bar( + curlshape_dFt_bar_buffer.GetData(), curl_dim, ndof); MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); @@ -3568,7 +3571,8 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, else // Dealing with scalar H1 field representing Az { /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - DenseMatrix curlshape_dFt_bar(curlshape_dFt_bar_buffer.GetData(), ndof, curl_dim); + DenseMatrix curlshape_dFt_bar( + curlshape_dFt_bar_buffer.GetData(), ndof, curl_dim); MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 6043bfd6..7c4eb5ca 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -154,14 +154,15 @@ class TorqueFunctional final auto &&air_attrs = options["air_attributes"].get>(); output.addOutputDomainIntegrator( new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs), - // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs), + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), + // attrs), air_attrs); } else { output.addOutputDomainIntegrator( new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs)); - // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); } } From 7ba001aac072cab736f22db129f7697475493763 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:54:39 -0600 Subject: [PATCH 17/72] change format workflow --- .github/workflows/format.yml | 3 +-- test/regression/test_magnetostatic_box2d.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2cdfcc8b..c18529f1 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -12,7 +12,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v4.0.0 + uses: jidicula/clang-format-action with: - clang-format-version: '12' check-path: ${{ matrix.path }} diff --git a/test/regression/test_magnetostatic_box2d.cpp b/test/regression/test_magnetostatic_box2d.cpp index 80830e96..60b64c56 100644 --- a/test/regression/test_magnetostatic_box2d.cpp +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -186,11 +186,11 @@ unique_ptr buildMesh(int nxy) auto vtx = mesh->GetVertex(verts[i]); if (vtx[1] <= 0.5) { - below = below & true; + below = below; } else { - below = below & false; + below = false; } } if (below) From 9f0fcb15ce575651f67fa114a6e1023315f6ca80 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:55:22 -0600 Subject: [PATCH 18/72] change format workflow --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index c18529f1..c3a73221 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -12,6 +12,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action + uses: jidicula/clang-format-action@4.8.0 with: check-path: ${{ matrix.path }} From 8f92345e9cfe5056680ca4f3472914a5bf952e88 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 2 Aug 2022 20:56:24 -0600 Subject: [PATCH 19/72] change format workflow --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index c3a73221..6a939fd9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -12,6 +12,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@4.8.0 + uses: jidicula/clang-format-action@v4.8.0 with: check-path: ${{ matrix.path }} From b5a1e4f7ae6dbfadf860693e9ab0c997a696236b Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 3 Aug 2022 10:39:18 -0600 Subject: [PATCH 20/72] add SetAttributes call in magnetostatic box regression tests to finallize setting attributes --- test/regression/test_magnetostatic_box.cpp | 8 +++++--- test/regression/test_magnetostatic_box2d.cpp | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/regression/test_magnetostatic_box.cpp b/test/regression/test_magnetostatic_box.cpp index 52e7c933..3e91d958 100644 --- a/test/regression/test_magnetostatic_box.cpp +++ b/test/regression/test_magnetostatic_box.cpp @@ -216,14 +216,14 @@ unique_ptr buildMesh(int nxy, int nz) bool below = true; for (int i = 0; i < verts.Size(); ++i) { - auto vtx = mesh->GetVertex(verts[i]); + auto *vtx = mesh->GetVertex(verts[i]); if (vtx[1] <= 0.5) { - below = below & true; + below = below; } else { - below = below & false; + below = false; } } if (below) @@ -235,5 +235,7 @@ unique_ptr buildMesh(int nxy, int nz) elem->SetAttribute(2); } } + mesh->SetAttributes(); + return mesh; } diff --git a/test/regression/test_magnetostatic_box2d.cpp b/test/regression/test_magnetostatic_box2d.cpp index 60b64c56..b28acedc 100644 --- a/test/regression/test_magnetostatic_box2d.cpp +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -111,6 +111,7 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", { // construct the solver, set the initial condition, and solve unique_ptr smesh = buildMesh(nxy); + MagnetostaticSolver solver(MPI_COMM_WORLD, options, std::move(smesh)); mfem::Vector state_tv(solver.getStateSize()); @@ -183,7 +184,8 @@ unique_ptr buildMesh(int nxy) bool below = true; for (int i = 0; i < verts.Size(); ++i) { - auto vtx = mesh->GetVertex(verts[i]); + auto *vtx = mesh->GetVertex(verts[i]); + // std::cout << "mesh vtx: " << vtx[0] << ", " << vtx[1] << "\n"; if (vtx[1] <= 0.5) { below = below; @@ -202,5 +204,7 @@ unique_ptr buildMesh(int nxy) elem->SetAttribute(2); } } + mesh->SetAttributes(); + return mesh; } From 75af98248bd3ad300d85acb0e7538b8f62c20d98 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Thu, 11 Aug 2022 15:56:52 -0600 Subject: [PATCH 21/72] added new ForceIntegrator3 that works for 2D and 3D em problems, hopefully easier to differentiate than ForceIntegrator2 --- .../electromagnetics/electromag_integ.cpp | 458 +++++++++++++++++- .../electromagnetics/electromag_integ.hpp | 67 ++- test/unit/test_electromag_integ.cpp | 311 +++++++++++- 3 files changed, 812 insertions(+), 24 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index ddc8e37a..cf3456ca 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -1,4 +1,4 @@ -#include +#include #include "coefficient.hpp" #include "electromag_integ.hpp" #include "mach_input.hpp" @@ -3432,7 +3432,7 @@ double HybridACLossFunctionalIntegrator::GetElementEnergy( return fun; } -double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, +double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun) { @@ -3461,21 +3461,252 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - DenseMatrix dshape(v_el.GetDof(), v_el.GetDim()); - DenseMatrix curlshape(ndof, curl_dim), curlshape_dFt(ndof, curl_dim); - DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); - Vector b_vec(curl_dim), b_hat(curl_dim); -#else + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#endif dshape.SetSize(v_el.GetDof(), v_el.GetDim()); curlshape.SetSize(ndof, curl_dim); curlshape_dFt.SetSize(ndof, curl_dim); - // dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); - b_vec.SetSize(curl_dim); - b_hat.SetSize(curl_dim); + + DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); + + // PointMat_bar.SetSize(space_dim, v_el.GetDof()); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + // double b_vec_bar_buffer[3]; + // Vector b_vec_bar(b_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + const double trans_weight = trans.Weight(); + /// holds quadrature weight + double w = ip.weight * trans_weight; + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else // Dealing with scalar H1 field representing Az + { + /// Not exactly the curl matrix, but since we just want the magnitude + /// of the curl it's okay + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + + // /// Compute d(energy)/d(b_mag) + // const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + + /// compute d(b_mag)/dJ + double db_magdJ_buffer[9]; + DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); + db_magdJ = 0.0; + if (dim == 3) + { + double b_hat_buffer[3]; + Vector b_hat(b_hat_buffer, curl_dim); + b_hat = 0.0; + + curlshape.AddMultTranspose(elfun, b_hat); + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); + + db_magdJ.Add(1.0 / (trans_weight * b_vec_norm), BB_hatT); + } + else + { + double b_adjJT_buffer[3]; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + + AddMult_a_VWt(-1 / (b_vec_norm * pow(trans_weight, 2)), b_vec, b_adjJT, db_magdJ); + } + + /// contract d(b_mag)/dJ with dJ/dX + dBdX = 0.0; + isotrans.JacobianRevDiff(db_magdJ, dBdX); + + double dBds = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int d = 0; d < curl_dim; ++d) + { + dBds += dBdX(d, j) * dXds(j, d); + } + } + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + auto force = dBds * energy_dot; + + v_el.CalcDShape(ip, dshape); + double JinvdJds_buffer[9]; + DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); + double dJds_buffer[9]; + DenseMatrix dJds(dJds_buffer, space_dim, space_dim); + MultAtB(dXds, dshape, dJds); + Mult(trans.InverseJacobian(), dJds, JinvdJds); + double JinvdJdsTrace = JinvdJds.Trace(); + + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double force2 = energy * JinvdJdsTrace; + fun -= (force + force2) * w; + } + return fun; + + // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // // fun += energy * w; + // double fun_bar = 1.0; + + // double energy_bar = 0.0; + // double w_bar = 0.0; + // energy_bar += fun_bar * w; + // w_bar += fun_bar * energy; + + // /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // double b_mag_bar = 0.0; + // const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + // b_mag_bar += energy_bar * energy_dot; + + // double b_vec_norm_bar = 0.0; + // double trans_weight_bar = 0.0; + // /// const double b_mag = b_vec_norm / trans_weight; + // b_vec_norm_bar += b_mag_bar / trans_weight; + // trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + // b_vec_bar = 0.0; + // /// const double b_vec_norm = b_vec.Norml2(); + // add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + // PointMat_bar = 0.0; + // if (dim == 3) + // { + // /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // // transposed dimensions of curlshape_dFt + // // so I don't have to transpose jac_bar later + // curlshape_dFt_bar.SetSize(curl_dim, ndof); + // MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + // /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + // double jac_bar_buffer[9]; + // DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + // jac_bar = 0.0; + // AddMult(curlshape_dFt_bar, curlshape, jac_bar); + // isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + // } + // else // Dealing with scalar H1 field representing Az + // { + // /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // curlshape_dFt_bar.SetSize(ndof, curl_dim); + // MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + + // /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + // double adj_bar_buffer[9]; + // DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + // adj_bar = 0.0; + // MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + // isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + // } + + // /// const double w = ip.weight * trans_weight; + // trans_weight_bar += w_bar * ip.weight; + + // isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + // // code to insert PointMat_bar into mesh_coords_bar; + // for (int j = 0; j < v_el.GetDof(); ++j) + // { + // for (int d = 0; d < space_dim; ++d) + // { + // fun -= dXds(j, d) * PointMat_bar(d, j); + // } + // } + // } + // return fun; +} + +double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + if (attrs.count(trans.Attribute) == 1) + { + return 0.0; + } + /// get the proper element, transformation, and v vector +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector vfun; #endif + int element = trans.ElementNo; + const auto &v_el = *v.FESpace()->GetFE(element); + v.FESpace()->GetElementVDofs(element, vdofs); + v.GetSubVector(vdofs, vfun); + DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); + if (vfun.Normlinf() < 1e-14) + { + return 0.0; + } + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; - Vector curlshape_dFt_bar_buffer(curl_dim * ndof); - DenseMatrix PointMat_bar(space_dim, v_el.GetDof()); +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + PointMat_bar.SetSize(space_dim, v_el.GetDof()); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation auto &isotrans = dynamic_cast(trans); @@ -3545,8 +3776,6 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, b_vec_norm_bar += b_mag_bar / trans_weight; trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); - double b_vec_bar_buffer[3]; - Vector b_vec_bar(b_vec_bar_buffer, curl_dim); b_vec_bar = 0.0; /// const double b_vec_norm = b_vec.Norml2(); add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); @@ -3555,10 +3784,9 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, if (dim == 3) { /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // transposed dimensions of curlshape_dFt so I don't have to transpose - // jac_bar later - DenseMatrix curlshape_dFt_bar( - curlshape_dFt_bar_buffer.GetData(), curl_dim, ndof); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); @@ -3571,8 +3799,7 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, else // Dealing with scalar H1 field representing Az { /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - DenseMatrix curlshape_dFt_bar( - curlshape_dFt_bar_buffer.GetData(), ndof, curl_dim); + curlshape_dFt_bar.SetSize(ndof, curl_dim); MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); @@ -3600,6 +3827,197 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, return fun; } +void ForceIntegrator2::AssembleElementVector(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elfun_bar) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + + elfun_bar.SetSize(ndof); + elfun_bar = 0.0; + if (attrs.count(trans.Attribute) == 1) + { + return; + } + + /// get the proper element, transformation, and v vector +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector vfun; +#endif + int element = trans.ElementNo; + const auto &v_el = *v.FESpace()->GetFE(element); + v.FESpace()->GetElementVDofs(element, vdofs); + v.GetSubVector(vdofs, vfun); + DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); + if (vfun.Normlinf() < 1e-14) + { + return; + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#endif + dshape.SetSize(v_el.GetDof(), space_dim); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + PointMat_bar.SetSize(space_dim, v_el.GetDof()); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + + /// holds quadrature weight + const double w = ip.weight * trans_weight; + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // energy_fun += energy * w; + double energy_fun_bar = 1.0; + + double energy_bar = 0.0; + double w_bar = 0.0; + energy_bar += energy_fun_bar * w; + w_bar += energy_fun_bar * energy; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + b_mag_bar += energy_bar * energy_dot; + + double b_vec_norm_bar = 0.0; + double trans_weight_bar = 0.0; + /// const double b_mag = b_vec_norm / trans_weight; + b_vec_norm_bar += b_mag_bar / trans_weight; + trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + b_vec_bar = 0.0; + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); + MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9]; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt_bar.SetSize(ndof, curl_dim); + MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9]; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + adj_bar = 0.0; + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + // for (int j = 0; j < v_el.GetDof(); ++j) + // { + // for (int d = 0; d < space_dim; ++d) + // { + // fun -= dXds(j, d) * PointMat_bar(d, j); + // } + // } + + /// start reverse pass... + double fun_bar = 1.0; + DenseMatrix PointMat_bar_bar(space_dim, v_el.GetDof()); + PointMat_bar_bar = 0.0; + // DenseMatrix dXds_bar(v_el.GetDof(), space_dim); + // dXds_bar = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int d = 0; d < space_dim; ++d) + { + /// fun -= dXds(j, d) * PointMat_bar(d, j); + PointMat_bar_bar(d, j) -= fun_bar * dXds(j, d); + // dXds_bar(j, d) -= fun_bar * PointMat_bar(d, j); // Don't need! + } + } + + /// isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + /// + + // Weight(Matrix) -> scalar + // WeightRevDiff(scalar) -> Matrix + // WeightRevDiffRevDiff(Matrix) -> scalar ? + } +} + double ForceIntegrator::GetElementEnergy(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun) diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 76b1b551..97eb1058 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1155,6 +1155,58 @@ class HybridACLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator #endif }; +class ForceIntegrator3 : public mfem::NonlinearFormIntegrator +{ +public: + ForceIntegrator3(StateCoefficient &nu, mfem::GridFunction &v) : nu(nu), v(v) + { } + + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] nu - model describing reluctivity + /// \param[in] v - the grid function containing virtual displacements for + /// each mesh node + /// \param[in] attrs - the regions the force is acting on + ForceIntegrator3(StateCoefficient &nu, + mfem::GridFunction &v, + std::unordered_set attrs) + : nu(nu), v(v), attrs(std::move(attrs)) + { } + + /// \brief - Compute element contribution to global force/torque + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \returns the element contribution to global force/torque + double GetElementEnergy(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) override; + + // /// \brief - Computes dJdu, for solving for the adjoint + // /// \param[in] el - the finite element + // /// \param[in] trans - defines the reference to physical element mapping + // /// \param[in] elfun - state vector of the element + // /// \param[out] elfun_bar - \partial J \partial u for this functional + // void AssembleElementVector(const mfem::FiniteElement &el, + // mfem::ElementTransformation &trans, + // const mfem::Vector &elfun, + // mfem::Vector &elfun_bar) override; + +private: + /// material dependent model describing reluctivity + StateCoefficient ν + /// grid function containing virtual displacements for each mesh node + mfem::GridFunction &v; + /// set of attributes the force is acting on + std::unordered_set attrs; +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshape; + mfem::DenseMatrix curlshape, curlshape_dFt, curlshape_dFt_bar; + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector vfun; +#endif +}; + class ForceIntegrator2 : public mfem::NonlinearFormIntegrator { public: @@ -1181,6 +1233,16 @@ class ForceIntegrator2 : public mfem::NonlinearFormIntegrator mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + /// \brief - Computes dJdu, for solving for the adjoint + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \param[out] elfun_bar - \partial J \partial u for this functional + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + private: /// material dependent model describing reluctivity StateCoefficient ν @@ -1189,8 +1251,9 @@ class ForceIntegrator2 : public mfem::NonlinearFormIntegrator /// set of attributes the force is acting on std::unordered_set attrs; #ifndef MFEM_THREAD_SAFE - mfem::DenseMatrix dshape, curlshape, curlshape_dFt, dBdX; - mfem::Vector b_vec, b_hat; + mfem::DenseMatrix dshape; + mfem::DenseMatrix curlshape, curlshape_dFt, curlshape_dFt_bar; + mfem::DenseMatrix PointMat_bar; mfem::Array vdofs; mfem::Vector vfun; #endif diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 7be04051..ca21b58b 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -1920,6 +1920,125 @@ TEST_CASE("calcMagneticEnergyDoubleDot") } } +TEST_CASE("ForceIntegrator3::GetElementEnergy") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + // NonLinearCoefficient nu; + LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator3(nu, v)); + + auto force = functional.GetEnergy(a); + + NonlinearForm energy(&fes); + energy.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + add(x_nodes, -delta, v, x_nodes); + auto dWds = -energy.GetEnergy(a); + add(x_nodes, 2*delta, v, x_nodes); + dWds += energy.GetEnergy(a); + dWds /= 2*delta; + + // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; + REQUIRE(force == Approx(-dWds)); + } + } +} + +TEST_CASE("ForceIntegrator3::GetElementEnergy - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + // NonLinearCoefficient nu; + LinearCoefficient nu; + + for (int p = 1; p <= 1; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + FunctionCoefficient apert(randState); + a.ProjectCoefficient(apert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator3(nu, v)); + + auto force = functional.GetEnergy(a); + + NonlinearForm energy(&fes); + energy.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + add(x_nodes, -delta, v, x_nodes); + auto dWds = -energy.GetEnergy(a); + add(x_nodes, 2*delta, v, x_nodes); + dWds += energy.GetEnergy(a); + dWds /= 2*delta; + + // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; + REQUIRE(force == Approx(-dWds)); + } + } +} + TEST_CASE("ForceIntegrator2::GetElementEnergy") { using namespace mfem; @@ -1927,12 +2046,70 @@ TEST_CASE("ForceIntegrator2::GetElementEnergy") double delta = 1e-5; + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + // H1_FECollection fec(p, dim); + ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator2(nu, v)); + + auto force = functional.GetEnergy(a); + + NonlinearForm energy(&fes); + energy.AddDomainIntegrator( + new mach::MagneticEnergyIntegrator(nu)); + + add(x_nodes, -delta, v, x_nodes); + auto dWds = -energy.GetEnergy(a); + add(x_nodes, 2*delta, v, x_nodes); + dWds += energy.GetEnergy(a); + dWds /= 2*delta; + + // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; + REQUIRE(force == Approx(-dWds)); + } + } +} + +TEST_CASE("ForceIntegrator2::GetElementEnergy - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + // generate a 8 element mesh int num_edge = 2; auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, Element::TRIANGLE); - // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - // Element::TETRAHEDRON); mesh.EnsureNodes(); const auto dim = mesh.SpaceDimension(); @@ -1984,6 +2161,136 @@ TEST_CASE("ForceIntegrator2::GetElementEnergy") } } +TEST_CASE("ForceIntegrator2::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state + GridFunction a(&fes); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator(nu, v)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("ForceIntegrator2::AssembleElementVector - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator2(nu, v)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + TEST_CASE("ForceIntegrator::GetElementEnergy") { using namespace mfem; From fece2cd97288e026917bca76016fe5d64196b06b Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 12:37:50 -0600 Subject: [PATCH 22/72] finished differentiating ForceIntegrator3 that handles 2D and 3D and is differentiated wrt the mesh and the state --- .../electromagnetics/electromag_integ.cpp | 817 ++++++++++++++++-- .../electromagnetics/electromag_integ.hpp | 55 +- test/unit/test_electromag_integ.cpp | 295 ++++++- 3 files changed, 1065 insertions(+), 102 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index cf3456ca..efb0cb5c 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3461,17 +3461,17 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; DenseMatrix curlshape; DenseMatrix curlshape_dFt; - DenseMatrix curlshape_dFt_bar; - DenseMatrix PointMat_bar; + DenseMatrix dBdX; #endif dshape.SetSize(v_el.GetDof(), v_el.GetDim()); curlshape.SetSize(ndof, curl_dim); curlshape_dFt.SetSize(ndof, curl_dim); + dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); - DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); - + // DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); // PointMat_bar.SetSize(space_dim, v_el.GetDof()); double b_vec_buffer[3]; @@ -3510,6 +3510,7 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, const double trans_weight = trans.Weight(); /// holds quadrature weight double w = ip.weight * trans_weight; + if (dim == 3) { el.CalcCurlShape(ip, curlshape); @@ -3523,16 +3524,11 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); + // b_vec = 0.0; + curlshape_dFt.MultTranspose(elfun, b_vec); const double b_vec_norm = b_vec.Norml2(); const double b_mag = b_vec_norm / trans_weight; - // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - - // /// Compute d(energy)/d(b_mag) - // const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); - /// compute d(b_mag)/dJ double db_magdJ_buffer[9]; DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); @@ -3560,7 +3556,9 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); - AddMult_a_VWt(-1 / (b_vec_norm * pow(trans_weight, 2)), b_vec, b_adjJT, db_magdJ); + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + + AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); } /// contract d(b_mag)/dJ with dJ/dX @@ -3570,9 +3568,9 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, double dBds = 0.0; for (int j = 0; j < v_el.GetDof(); ++j) { - for (int d = 0; d < curl_dim; ++d) + for (int k = 0; k < space_dim; ++k) { - dBds += dBdX(d, j) * dXds(j, d); + dBds += dBdX(k, j) * dXds(j, k); } } const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); @@ -3592,76 +3590,729 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, fun -= (force + force2) * w; } return fun; +} - // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - // // fun += energy * w; - // double fun_bar = 1.0; - - // double energy_bar = 0.0; - // double w_bar = 0.0; - // energy_bar += fun_bar * w; - // w_bar += fun_bar * energy; - - // /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - // double b_mag_bar = 0.0; - // const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); - // b_mag_bar += energy_bar * energy_dot; - - // double b_vec_norm_bar = 0.0; - // double trans_weight_bar = 0.0; - // /// const double b_mag = b_vec_norm / trans_weight; - // b_vec_norm_bar += b_mag_bar / trans_weight; - // trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); - - // b_vec_bar = 0.0; - // /// const double b_vec_norm = b_vec.Norml2(); - // add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); - - // PointMat_bar = 0.0; - // if (dim == 3) - // { - // /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // // transposed dimensions of curlshape_dFt - // // so I don't have to transpose jac_bar later - // curlshape_dFt_bar.SetSize(curl_dim, ndof); - // MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); - - // /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - // double jac_bar_buffer[9]; - // DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); - // jac_bar = 0.0; - // AddMult(curlshape_dFt_bar, curlshape, jac_bar); - // isotrans.JacobianRevDiff(jac_bar, PointMat_bar); - // } - // else // Dealing with scalar H1 field representing Az - // { - // /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // curlshape_dFt_bar.SetSize(ndof, curl_dim); - // MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); - - // /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - // double adj_bar_buffer[9]; - // DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); - // adj_bar = 0.0; - // MultAtB(curlshape, curlshape_dFt_bar, adj_bar); - // isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); - // } - - // /// const double w = ip.weight * trans_weight; - // trans_weight_bar += w_bar * ip.weight; - - // isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); - - // // code to insert PointMat_bar into mesh_coords_bar; - // for (int j = 0; j < v_el.GetDof(); ++j) - // { - // for (int d = 0; d < space_dim; ++d) - // { - // fun -= dXds(j, d) * PointMat_bar(d, j); - // } - // } - // } - // return fun; +void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elfun_bar) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + + elfun_bar.SetSize(ndof); + elfun_bar = 0.0; + if (attrs.count(trans.Attribute) == 1) + { + return; + } + + /// get the proper element, transformation, and v vector +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector vfun; +#endif + int element = trans.ElementNo; + const auto &v_el = *v.FESpace()->GetFE(element); + v.FESpace()->GetElementVDofs(element, vdofs); + v.GetSubVector(vdofs, vfun); + DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); + if (vfun.Normlinf() < 1e-14) + { + return; + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix dBdX; +#endif + dshape.SetSize(v_el.GetDof(), v_el.GetDim()); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + + /// holds quadrature weight + const double w = ip.weight * trans_weight; + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else // Dealing with scalar H1 field representing Az + { + /// Not exactly the curl matrix, but since we just want the magnitude + /// of the curl it's okay + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + /// compute d(b_mag)/dJ + double db_magdJ_buffer[9]; + DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); + db_magdJ = 0.0; + if (dim == 3) + { + double b_hat_buffer[3]; + Vector b_hat(b_hat_buffer, curl_dim); + b_hat = 0.0; + + curlshape.AddMultTranspose(elfun, b_hat); + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); + + db_magdJ.Add(1.0 / (trans_weight * b_vec_norm), BB_hatT); + } + else + { + double b_adjJT_buffer[3]; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + + AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + } + + /// contract d(b_mag)/dJ with dJ/dX + dBdX = 0.0; + isotrans.JacobianRevDiff(db_magdJ, dBdX); + + double dBds = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int k = 0; k < space_dim; ++k) + { + dBds += dBdX(k, j) * dXds(j, k); + } + } + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + // auto force = dBds * energy_dot; + + v_el.CalcDShape(ip, dshape); + double JinvdJds_buffer[9]; + DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); + double dJds_buffer[9]; + DenseMatrix dJds(dJds_buffer, space_dim, space_dim); + MultAtB(dXds, dshape, dJds); + Mult(trans.InverseJacobian(), dJds, JinvdJds); + double JinvdJdsTrace = JinvdJds.Trace(); + + // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // double force2 = energy * JinvdJdsTrace; + // fun -= (force + force2) * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun -= (force + force2) * w; + double force_bar = 0.0; + double force2_bar = 0.0; + force_bar -= fun_bar * w; + force2_bar -= fun_bar * w; + + /// double force2 = energy * JinvdJdsTrace; + double energy_bar = force2_bar * JinvdJdsTrace; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + b_mag_bar += energy_bar * energy_dot; + + /// auto force = dBds * energy_dot; + double dBds_bar = force_bar * energy_dot; + double energy_dot_bar = force_bar * dBds; + + /// double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + auto energy_double_dot = + calcMagneticEnergyDoubleDot(trans, ip, nu, b_mag); + b_mag_bar += energy_dot_bar * energy_double_dot; + + DenseMatrix dBdX_bar(v_el.GetDim(), v_el.GetDof()); + dBdX_bar = 0.0; // same shape as dBdX + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int k = 0; k < space_dim; ++k) + { + /// dBds += dBdX(k, j) * dXds(j, k); + dBdX_bar(k, j) += dBds_bar * dXds(j, k); + } + } + + /// isotrans.JacobianRevDiff(db_magdJ, dBdX); + /// aka AddMultABt(db_magdJ, dshape, dBdX); + double db_magdJ_bar_buffer[9]; + DenseMatrix db_magdJ_bar(db_magdJ_bar_buffer, space_dim, space_dim); + Mult(dBdX_bar, dshape, db_magdJ_bar); + // db_magdJ_bar = 0.0; + // v_el.CalcDShape(ip, dshape); + // AddMult(dBdX_bar, dshape, db_magdJ_bar); + + double b_vec_norm_bar = 0.0; + // double trans_weight_bar = 0.0; + if (dim == 3) + { + double b_hat_buffer[3]; + Vector b_hat(b_hat_buffer, curl_dim); + curlshape.MultTranspose(elfun, b_hat); + + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + double BB_hatT_bar_buffer[9]; + DenseMatrix BB_hatT_bar(BB_hatT_bar_buffer, curl_dim, curl_dim); + BB_hatT_bar = 0.0; + + /// db_magdJ.Add(1.0 / (b_vec_norm * trans_weight), BB_hatT); + BB_hatT_bar.Add(1.0 / (b_vec_norm * trans_weight), db_magdJ_bar); + + for (int j = 0; j < curl_dim; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + b_vec_norm_bar -= db_magdJ_bar(j, k) * BB_hatT(j, k) / + (pow(b_vec_norm, 2) * trans_weight); + // trans_weight_bar -= db_magdJ_bar(j, k) * BB_hatT(j, k) / + // (b_vec_norm * pow(trans_weight, 2)); + } + } + + /// db_magdJ.Transpose(); + db_magdJ_bar.Transpose(); + + /// db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + /// trans.AdjugateJacobian()); + for (int j = 0; j < curl_dim; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + b_vec_norm_bar -= db_magdJ_bar(j, k) * + trans.AdjugateJacobian()(j, k) / + pow(trans_weight, 2); + // trans_weight_bar += db_magdJ_bar(j, k) * b_vec_norm * + // trans.AdjugateJacobian()(j, k) / + // pow(trans_weight, 3); + } + } + + double b_hat_bar_buffer[3]; + Vector b_hat_bar(b_hat_bar_buffer, curl_dim); + + /// MultVWt(b_vec, b_hat, BB_hatT); + BB_hatT_bar.Mult(b_hat, b_vec_bar); + BB_hatT_bar.MultTranspose(b_vec, b_hat_bar); + + /// curlshape.AddMultTranspose(elfun, b_hat); + curlshape.AddMult(b_hat_bar, elfun_bar); + } + else + { + double b_adjJT_buffer[3]; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + + /// AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + double b_adjJT_var_buffer[3]; + Vector b_adjJT_bar(b_adjJT_var_buffer, curl_dim); + + b_vec_bar = 0.0; + db_magdJ_bar.AddMult_a(a, b_adjJT, b_vec_bar); + b_adjJT_bar = 0.0; + db_magdJ_bar.AddMultTranspose_a(a, b_vec, b_adjJT_bar); + double a_bar = 0; + for (int j = 0; j < space_dim; ++j) + { + for (int k = 0; k < space_dim; ++k) + { + a_bar += db_magdJ_bar(j, k) * db_magdJ(j, k); + } + } + a_bar /= a; + + /// double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + b_vec_norm_bar += a_bar / pow(b_vec_norm * trans_weight, 2); + // trans_weight_bar = 2 * a_bar / (b_vec_norm * pow(trans_weight, 3)); + + /// trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + trans.AdjugateJacobian().AddMultTranspose(b_adjJT_bar, b_vec_bar); + } + + /// const double b_mag = b_vec_norm / trans_weight; + b_vec_norm_bar += b_mag_bar / trans_weight; + // trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); + + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + /// curlshape_dFt.MultTranspose(elfun, b_vec); + curlshape_dFt.AddMult(b_vec_bar, elfun_bar); + } +} + +void ForceIntegratorMeshSens3::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + + auto &attrs = force_integ.attrs; + if (attrs.count(trans.Attribute) == 1) + { + return; + } + + auto &v = force_integ.v; + +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector vfun; + Vector elfun; +#endif + auto &vdofs = force_integ.vdofs; + auto &vfun = force_integ.vfun; + + /// get the proper element, transformation, and v vector + const auto &v_el = *v.FESpace()->GetFE(element); + v.FESpace()->GetElementVDofs(element, vdofs); + v.GetSubVector(vdofs, vfun); + DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); + if (vfun.Normlinf() < 1e-14) + { + return; + } + + /// get the proper element, transformation, and state vector + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix dBdX; + DenseMatrix PointMat_bar; +#else + auto &dshape = force_integ.dshape; + auto &curlshape = force_integ.curlshape; + auto &curlshape_dFt = force_integ.curlshape_dFt; + auto &dBdX = force_integ.dBdX; +#endif + dshape.SetSize(v_el.GetDof(), v_el.GetDim()); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + DenseMatrix curlshape_dFt_bar; + + double b_vec_buffer[3]; + Vector b_vec(b_vec_buffer, curl_dim); + + double b_vec_bar_buffer[3]; + Vector b_vec_bar(b_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &nu = force_integ.nu; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + + /// holds quadrature weight + const double w = ip.weight * trans_weight; + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else // Dealing with scalar H1 field representing Az + { + /// Not exactly the curl matrix, but since we just want the magnitude + /// of the curl it's okay + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; + + /// compute d(b_mag)/dJ + double db_magdJ_buffer[9]; + DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); + db_magdJ = 0.0; + if (dim == 3) + { + double b_hat_buffer[3]; + Vector b_hat(b_hat_buffer, curl_dim); + b_hat = 0.0; + + curlshape.AddMultTranspose(elfun, b_hat); + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); + + db_magdJ.Add(1.0 / (trans_weight * b_vec_norm), BB_hatT); + } + else + { + double b_adjJT_buffer[3]; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + + AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + } + + /// contract d(b_mag)/dJ with dJ/dX + dBdX = 0.0; + isotrans.JacobianRevDiff(db_magdJ, dBdX); + + double dBds = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int k = 0; k < space_dim; ++k) + { + dBds += dBdX(k, j) * dXds(j, k); + } + } + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + auto force = dBds * energy_dot; + + v_el.CalcDShape(ip, dshape); + double JinvdJds_buffer[9]; + DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); + double dJds_buffer[9]; + DenseMatrix dJds(dJds_buffer, space_dim, space_dim); + MultAtB(dXds, dshape, dJds); + Mult(trans.InverseJacobian(), dJds, JinvdJds); + double JinvdJdsTrace = JinvdJds.Trace(); + + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double force2 = energy * JinvdJdsTrace; + // fun -= (force + force2) * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun -= (force + force2) * w; + double force_bar = 0.0; + double force2_bar = 0.0; + double w_bar = 0.0; + force_bar -= fun_bar * w; + force2_bar -= fun_bar * w; + w_bar -= fun_bar * (force + force2); + + /// double force2 = energy * JinvdJdsTrace; + double energy_bar = force2_bar * JinvdJdsTrace; + double JinvdJdsTrace_bar = force2_bar * energy; + + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + b_mag_bar += energy_bar * energy_dot; + + /// double JinvdJdsTrace = JinvdJds.Trace(); + double JinvdJds_bar_buffer[9]; + DenseMatrix JinvdJds_bar(JinvdJds_bar_buffer, space_dim, space_dim); + JinvdJds_bar.Diag(JinvdJdsTrace_bar, space_dim); + + /// Mult(trans.InverseJacobian(), dJds, JinvdJds); + double dJds_bar_buffer[9]; + DenseMatrix dJds_bar(dJds_bar_buffer, space_dim, space_dim); + double inv_jac_bar_buffer[9]; + DenseMatrix inv_jac_bar(inv_jac_bar_buffer, space_dim, space_dim); + MultABt(JinvdJds_bar, dJds, inv_jac_bar); + MultAtB(trans.InverseJacobian(), JinvdJds_bar, dJds_bar); + + /// Matrix inverse reverse mode rule: + /// C = A^-1, + /// A_bar = -C^T * C_bar * C^T + double scratch_buffer[9] = {0}; + DenseMatrix scratch(scratch_buffer, space_dim, space_dim); + double jac_bar_buffer[9] = {0}; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + MultAtB(trans.InverseJacobian(), inv_jac_bar, scratch); + AddMult_a_ABt(-1.0, scratch, trans.InverseJacobian(), jac_bar); + + /// MultAtB(dXds, dshape, dJds); // does not depend on mesh nodes + /// v_el.CalcDShape(ip, dshape); // does not depend on mesh nodes + + /// auto force = dBds * energy_dot; + double dBds_bar = force_bar * energy_dot; + double energy_dot_bar = force_bar * dBds; + + /// double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + auto energy_double_dot = + calcMagneticEnergyDoubleDot(trans, ip, nu, b_mag); + b_mag_bar += energy_dot_bar * energy_double_dot; + + /// TODO: replace with class matrix + DenseMatrix dBdX_bar(v_el.GetDim(), v_el.GetDof()); + dBdX_bar = 0.0; // same shape as dBdX + for (int j = 0; j < v_el.GetDof(); ++j) + { + for (int k = 0; k < space_dim; ++k) + { + /// dBds += dBdX(k, j) * dXds(j, k); + dBdX_bar(k, j) += dBds_bar * dXds(j, k); + } + } + + /// isotrans.JacobianRevDiff(db_magdJ, dBdX); + /// aka AddMultABt(db_magdJ, dshape, dBdX); + double db_magdJ_bar_buffer[9] = {0}; + DenseMatrix db_magdJ_bar(db_magdJ_bar_buffer, space_dim, space_dim); + Mult(dBdX_bar, dshape, db_magdJ_bar); + + // /// isotrans.JacobianRevDiff(dBmdJ, dBdX); + // /// aka AddMultABt(dBmdJ, dshape, dBdX); + // DenseMatrix dBmdJ_bar(dimc); + // dBmdJ_bar = 0.0; + // v_el.CalcDShape(ip, dshape); + // AddMult(dBdX_bar, dshape, dBmdJ_bar); + + + double b_vec_norm_bar = 0.0; + double trans_weight_bar = 0.0; + double adj_jac_bar_buffer[9] = {0}; + DenseMatrix adj_jac_bar(adj_jac_bar_buffer, space_dim, space_dim); + if (dim == 3) + { + double b_hat_buffer[3]; + Vector b_hat(b_hat_buffer, curl_dim); + curlshape.MultTranspose(elfun, b_hat); + + double BB_hatT_buffer[9]; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); + + double BB_hatT_bar_buffer[9]; + DenseMatrix BB_hatT_bar(BB_hatT_bar_buffer, curl_dim, curl_dim); + BB_hatT_bar = 0.0; + + /// db_magdJ.Add(1.0 / (b_vec_norm * trans_weight), BB_hatT); + BB_hatT_bar.Add(1.0 / (b_vec_norm * trans_weight), db_magdJ_bar); + + for (int j = 0; j < curl_dim; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + b_vec_norm_bar -= db_magdJ_bar(j, k) * BB_hatT(j, k) / + (pow(b_vec_norm, 2) * trans_weight); + trans_weight_bar -= db_magdJ_bar(j, k) * BB_hatT(j, k) / + (b_vec_norm * pow(trans_weight, 2)); + } + } + + /// db_magdJ.Transpose(); + db_magdJ_bar.Transpose(); + + /// db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + /// trans.AdjugateJacobian()); + adj_jac_bar.Add(-b_vec_norm / pow(trans_weight, 2), db_magdJ_bar); + for (int j = 0; j < space_dim; ++j) + { + for (int k = 0; k < space_dim; ++k) + { + b_vec_norm_bar -= db_magdJ_bar(j, k) * + trans.AdjugateJacobian()(j, k) / + pow(trans_weight, 2); + trans_weight_bar += 2 * db_magdJ_bar(j, k) * b_vec_norm * + trans.AdjugateJacobian()(j, k) / + pow(trans_weight, 3); + } + } + + // double b_hat_bar_buffer[3]; + // Vector b_hat_bar(b_hat_bar_buffer, curl_dim); + + /// MultVWt(b_vec, b_hat, BB_hatT); + BB_hatT_bar.Mult(b_hat, b_vec_bar); + // BB_hatT_bar.MultTranspose(b_vec, b_hat_bar); + // does not depend on mesh nodes + + /// curlshape.AddMultTranspose(elfun, b_hat); + // does not depend on mesh nodes + } + else + { + double b_adjJT_buffer[3]; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + + /// AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + double b_adjJT_var_buffer[3]; + Vector b_adjJT_bar(b_adjJT_var_buffer, curl_dim); + + b_vec_bar = 0.0; + db_magdJ_bar.AddMult_a(a, b_adjJT, b_vec_bar); + b_adjJT_bar = 0.0; + db_magdJ_bar.AddMultTranspose_a(a, b_vec, b_adjJT_bar); + double a_bar = 0; + for (int j = 0; j < space_dim; ++j) + { + for (int k = 0; k < space_dim; ++k) + { + a_bar += db_magdJ_bar(j, k) * db_magdJ(j, k); + } + } + a_bar /= a; + + /// double a = -1 / (b_vec_norm * pow(trans_weight, 2)); + b_vec_norm_bar += a_bar / pow(b_vec_norm * trans_weight, 2); + trans_weight_bar = 2 * a_bar / (b_vec_norm * pow(trans_weight, 3)); + + /// trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + trans.AdjugateJacobian().AddMultTranspose(b_adjJT_bar, b_vec_bar); + MultVWt(b_adjJT_bar, b_vec, adj_jac_bar); + } + + /// const double b_mag = b_vec_norm / trans.Weight(); + b_vec_norm_bar += b_mag_bar / trans.Weight(); + trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans.Weight(), 2); + + /// const double b_vec_norm = b_vec.Norml2(); + add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); + + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); + MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt_bar.SetSize(ndof, curl_dim); + MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + MultAtB(curlshape, curlshape_dFt_bar, scratch); + adj_jac_bar += scratch; + } + + PointMat_bar = 0.0; + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); + + /// const double w = ip.weight * trans.Weight(); + trans_weight_bar += w_bar * ip.weight; + + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + mesh_coords_bar(k * mesh_ndof + j) += PointMat_bar(k, j); + } + } + } } double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 97eb1058..f34bee3e 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1181,15 +1181,15 @@ class ForceIntegrator3 : public mfem::NonlinearFormIntegrator mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; - // /// \brief - Computes dJdu, for solving for the adjoint - // /// \param[in] el - the finite element - // /// \param[in] trans - defines the reference to physical element mapping - // /// \param[in] elfun - state vector of the element - // /// \param[out] elfun_bar - \partial J \partial u for this functional - // void AssembleElementVector(const mfem::FiniteElement &el, - // mfem::ElementTransformation &trans, - // const mfem::Vector &elfun, - // mfem::Vector &elfun_bar) override; + /// \brief - Computes dJdu, for solving for the adjoint + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \param[out] elfun_bar - \partial J \partial u for this functional + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; private: /// material dependent model describing reluctivity @@ -1201,9 +1201,44 @@ class ForceIntegrator3 : public mfem::NonlinearFormIntegrator #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix dshape; mfem::DenseMatrix curlshape, curlshape_dFt, curlshape_dFt_bar; - mfem::DenseMatrix PointMat_bar; + mfem::DenseMatrix dBdX; mfem::Array vdofs; mfem::Vector vfun; +#endif + /// class that implements mesh sensitivities for ForceIntegrator + friend class ForceIntegratorMeshSens3; +}; + +/// Linear form integrator to assemble the vector dJdX for the ForceIntegrator +class ForceIntegratorMeshSens3 : public mfem::LinearFormIntegrator +{ +public: + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] state - the state vector to evaluate force at + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrators + ForceIntegratorMeshSens3(mfem::GridFunction &state, ForceIntegrator3 &integ) + : state(state), force_integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating force + mfem::GridFunction &state; + /// reference to primal integrator + ForceIntegrator3 &force_integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; + mfem::Vector elfun; #endif }; diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index ca21b58b..0baadf09 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -1934,8 +1934,8 @@ TEST_CASE("ForceIntegrator3::GetElementEnergy") mesh.EnsureNodes(); const auto dim = mesh.SpaceDimension(); - // NonLinearCoefficient nu; - LinearCoefficient nu; + NonLinearCoefficient nu; + // LinearCoefficient nu; for (int p = 1; p <= 4; ++p) { @@ -1993,10 +1993,10 @@ TEST_CASE("ForceIntegrator3::GetElementEnergy - 2D") mesh.EnsureNodes(); const auto dim = mesh.SpaceDimension(); - // NonLinearCoefficient nu; - LinearCoefficient nu; + NonLinearCoefficient nu; + // LinearCoefficient nu; - for (int p = 1; p <= 1; ++p) + for (int p = 1; p <= 4; ++p) { DYNAMIC_SECTION("...for degree p = " << p) { @@ -2039,6 +2039,280 @@ TEST_CASE("ForceIntegrator3::GetElementEnergy - 2D") } } +TEST_CASE("ForceIntegrator3::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 1; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(dim, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state + GridFunction a(&fes); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator3(nu, v)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("ForceIntegrator3::AssembleElementVector - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator3(nu, v)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("ForceIntegratorMeshSens3::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient pert(3, randVectorState); + v.ProjectCoefficient(pert); + + // initialize state; here we randomly perturb a constant state + GridFunction a(&fes); + a.ProjectCoefficient(pert); + + auto *integ = new mach::ForceIntegrator3(nu, v); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::ForceIntegratorMeshSens3(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + double delta = 1e-5; + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + +TEST_CASE("ForceIntegratorMeshSens3::AssembleRHSElementVect - 2D") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + // ND_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::ForceIntegrator3(nu, v); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::ForceIntegratorMeshSens3(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + double delta = 1e-5; + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("ForceIntegrator2::GetElementEnergy") { using namespace mfem; @@ -2360,16 +2634,19 @@ TEST_CASE("ForceIntegrator::AssembleElementVector") double delta = 1e-5; // generate a 6 element mesh - int num_edge = 2; + // int num_edge = 2; + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON, + // 1.0, 1.0, 1.0, true); + int num_edge = 1; auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - Element::TETRAHEDRON, - 1.0, 1.0, 1.0, true); + Element::TETRAHEDRON); mesh.EnsureNodes(); NonLinearCoefficient nu; /// construct elements - for (int p = 1; p <= 4; ++p) + for (int p = 1; p <= 1; ++p) { DYNAMIC_SECTION("...for degree p = " << p) { From ae3dd595793ed2ebfc47a74376ec55842695b167 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 12:38:20 -0600 Subject: [PATCH 23/72] make format --- src/physics/electromagnetics/electromag_integ.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index efb0cb5c..6e2c0edb 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3839,7 +3839,7 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, trans.AdjugateJacobian()(j, k) / pow(trans_weight, 2); // trans_weight_bar += db_magdJ_bar(j, k) * b_vec_norm * - // trans.AdjugateJacobian()(j, k) / + // trans.AdjugateJacobian()(j, k) / // pow(trans_weight, 3); } } @@ -4121,7 +4121,7 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( /// Matrix inverse reverse mode rule: /// C = A^-1, - /// A_bar = -C^T * C_bar * C^T + /// A_bar = -C^T * C_bar * C^T double scratch_buffer[9] = {0}; DenseMatrix scratch(scratch_buffer, space_dim, space_dim); double jac_bar_buffer[9] = {0}; @@ -4167,7 +4167,6 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( // v_el.CalcDShape(ip, dshape); // AddMult(dBdX_bar, dshape, dBmdJ_bar); - double b_vec_norm_bar = 0.0; double trans_weight_bar = 0.0; double adj_jac_bar_buffer[9] = {0}; @@ -4214,7 +4213,7 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( trans.AdjugateJacobian()(j, k) / pow(trans_weight, 2); trans_weight_bar += 2 * db_magdJ_bar(j, k) * b_vec_norm * - trans.AdjugateJacobian()(j, k) / + trans.AdjugateJacobian()(j, k) / pow(trans_weight, 3); } } From 58c9514196e4219a2f4abb3f94cd84de51eb504c Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 14:03:42 -0600 Subject: [PATCH 24/72] zero initializing all stack-allocated vector/matrix buffers in electromag integs --- .../electromagnetics/electromag_integ.cpp | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 6e2c0edb..23c1b4ba 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -106,7 +106,7 @@ void NonlinearDiffusionIntegrator::AssembleElementVector( dshape.SetSize(ndof, dim); dshapedxt.SetSize(ndof, space_dim); - double pointflux_buffer[3]; + double pointflux_buffer[3] = {}; Vector pointflux(pointflux_buffer, space_dim); const IntegrationRule *ir = IntRule; @@ -186,7 +186,7 @@ void NonlinearDiffusionIntegrator::AssembleElementGrad( point_flux_2_dot.SetSize(ndof, space_dim); pointflux_norm_dot.SetSize(ndof); - double pointflux_buffer[3]; + double pointflux_buffer[3] = {}; Vector pointflux(pointflux_buffer, space_dim); const IntegrationRule *ir = IntRule; @@ -282,7 +282,7 @@ void MagnetizationSource2DIntegrator::AssembleRHSElementVect( dshapedxt.SetSize(ndof, space_dim); scratch.SetSize(ndof); - double mag_flux_buffer[3]; + double mag_flux_buffer[3] = {}; Vector mag_flux(mag_flux_buffer, space_dim); const IntegrationRule *ir = IntRule; @@ -358,7 +358,7 @@ void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, // b_vec.SetSize(dimc); #endif - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, dimc); const IntegrationRule *ir = IntRule; @@ -432,7 +432,7 @@ void CurlCurlNLFIntegrator::AssembleElementGrad( scratch.SetSize(ndof); #endif - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, dimc); const IntegrationRule *ir = IntRule; @@ -642,9 +642,9 @@ void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( auto &nu = integ.model; /// these vector's size is the spatial dimension we can stack allocate - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, dim); - double curl_psi_buffer[3]; + double curl_psi_buffer[3] = {}; Vector curl_psi(curl_psi_buffer, dim); // cast the ElementTransformation @@ -719,13 +719,13 @@ void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans.Weight(), 2); /// const double b_vec_norm = b_vec.Norml2(); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, dim); b_vec_bar = 0.0; add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); /// const double curl_psi_dot_b = curl_psi * b_vec; - double curl_psi_bar_buffer[3]; + double curl_psi_bar_buffer[3] = {}; Vector curl_psi_bar(curl_psi_bar_buffer, dim); curl_psi_bar = 0.0; add(curl_psi_bar, curl_psi_dot_b_bar, b_vec, curl_psi_bar); @@ -738,7 +738,7 @@ void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( AddMultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; + double jac_bar_buffer[9] = {}; DenseMatrix jac_bar(jac_bar_buffer, dim, dim); jac_bar = 0.0; AddMult(curlshape_dFt_bar, curlshape, jac_bar); @@ -1476,7 +1476,7 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, curlshape.SetSize(ndof, curl_dim); curlshape_dFt.SetSize(ndof, curl_dim); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); const IntegrationRule *ir = IntRule; @@ -1550,10 +1550,10 @@ void MagneticEnergyIntegrator::AssembleElementVector( // Vector curlshape_dFt_bar_buffer(curl_dim * ndof); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); const IntegrationRule *ir = IntRule; @@ -1676,10 +1676,10 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( // Vector curlshape_dFt_bar_buffer(curl_dim * ndof); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -1768,7 +1768,7 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; + double jac_bar_buffer[9] = {}; DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); jac_bar = 0.0; AddMult(curlshape_dFt_bar, curlshape, jac_bar); @@ -1784,7 +1784,7 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - double adj_bar_buffer[9]; + double adj_bar_buffer[9] = {}; DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); // adj_bar = 0.0; MultAtB(curlshape, curlshape_dFt_bar, adj_bar); @@ -2422,7 +2422,7 @@ double BNormSquaredIntegrator::GetElementEnergy(const FiniteElement &el, curlshape_dFt.SetSize(ndof, dimc); #endif - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, dimc); const IntegrationRule *ir = IntRule; @@ -3474,10 +3474,10 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, // DenseMatrix dBdX(v_el.GetDim(), v_el.GetDof()); // PointMat_bar.SetSize(space_dim, v_el.GetDof()); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - // double b_vec_bar_buffer[3]; + // double b_vec_bar_buffer[3] = {}; // Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -3530,17 +3530,17 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, const double b_mag = b_vec_norm / trans_weight; /// compute d(b_mag)/dJ - double db_magdJ_buffer[9]; + double db_magdJ_buffer[9] = {}; DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); db_magdJ = 0.0; if (dim == 3) { - double b_hat_buffer[3]; + double b_hat_buffer[3] = {}; Vector b_hat(b_hat_buffer, curl_dim); b_hat = 0.0; curlshape.AddMultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); @@ -3552,7 +3552,7 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, } else { - double b_adjJT_buffer[3]; + double b_adjJT_buffer[3] = {}; Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); @@ -3577,9 +3577,9 @@ double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, auto force = dBds * energy_dot; v_el.CalcDShape(ip, dshape); - double JinvdJds_buffer[9]; + double JinvdJds_buffer[9] = {}; DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); - double dJds_buffer[9]; + double dJds_buffer[9] = {}; DenseMatrix dJds(dJds_buffer, space_dim, space_dim); MultAtB(dXds, dshape, dJds); Mult(trans.InverseJacobian(), dJds, JinvdJds); @@ -3636,10 +3636,10 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, curlshape_dFt.SetSize(ndof, curl_dim); dBdX.SetSize(v_el.GetDim(), v_el.GetDof()); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -3692,17 +3692,17 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, const double b_mag = b_vec_norm / trans_weight; /// compute d(b_mag)/dJ - double db_magdJ_buffer[9]; + double db_magdJ_buffer[9] = {}; DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); db_magdJ = 0.0; if (dim == 3) { - double b_hat_buffer[3]; + double b_hat_buffer[3] = {}; Vector b_hat(b_hat_buffer, curl_dim); b_hat = 0.0; curlshape.AddMultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); @@ -3714,7 +3714,7 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, } else { - double b_adjJT_buffer[3]; + double b_adjJT_buffer[3] = {}; Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); @@ -3739,9 +3739,9 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, // auto force = dBds * energy_dot; v_el.CalcDShape(ip, dshape); - double JinvdJds_buffer[9]; + double JinvdJds_buffer[9] = {}; DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); - double dJds_buffer[9]; + double dJds_buffer[9] = {}; DenseMatrix dJds(dJds_buffer, space_dim, space_dim); MultAtB(dXds, dshape, dJds); Mult(trans.InverseJacobian(), dJds, JinvdJds); @@ -3789,7 +3789,7 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, /// isotrans.JacobianRevDiff(db_magdJ, dBdX); /// aka AddMultABt(db_magdJ, dshape, dBdX); - double db_magdJ_bar_buffer[9]; + double db_magdJ_bar_buffer[9] = {}; DenseMatrix db_magdJ_bar(db_magdJ_bar_buffer, space_dim, space_dim); Mult(dBdX_bar, dshape, db_magdJ_bar); // db_magdJ_bar = 0.0; @@ -3800,15 +3800,15 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, // double trans_weight_bar = 0.0; if (dim == 3) { - double b_hat_buffer[3]; + double b_hat_buffer[3] = {}; Vector b_hat(b_hat_buffer, curl_dim); curlshape.MultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); - double BB_hatT_bar_buffer[9]; + double BB_hatT_bar_buffer[9] = {}; DenseMatrix BB_hatT_bar(BB_hatT_bar_buffer, curl_dim, curl_dim); BB_hatT_bar = 0.0; @@ -3844,7 +3844,7 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, } } - double b_hat_bar_buffer[3]; + double b_hat_bar_buffer[3] = {}; Vector b_hat_bar(b_hat_bar_buffer, curl_dim); /// MultVWt(b_vec, b_hat, BB_hatT); @@ -3856,14 +3856,14 @@ void ForceIntegrator3::AssembleElementVector(const FiniteElement &el, } else { - double b_adjJT_buffer[3]; + double b_adjJT_buffer[3] = {}; Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); double a = -1 / (b_vec_norm * pow(trans_weight, 2)); /// AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); - double b_adjJT_var_buffer[3]; + double b_adjJT_var_buffer[3] = {}; Vector b_adjJT_bar(b_adjJT_var_buffer, curl_dim); b_vec_bar = 0.0; @@ -3971,10 +3971,10 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( DenseMatrix curlshape_dFt_bar; - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -4028,17 +4028,17 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( const double b_mag = b_vec_norm / trans_weight; /// compute d(b_mag)/dJ - double db_magdJ_buffer[9]; + double db_magdJ_buffer[9] = {}; DenseMatrix db_magdJ(db_magdJ_buffer, space_dim, space_dim); db_magdJ = 0.0; if (dim == 3) { - double b_hat_buffer[3]; + double b_hat_buffer[3] = {}; Vector b_hat(b_hat_buffer, curl_dim); b_hat = 0.0; curlshape.AddMultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); @@ -4050,7 +4050,7 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( } else { - double b_adjJT_buffer[3]; + double b_adjJT_buffer[3] = {}; Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); @@ -4075,9 +4075,9 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( auto force = dBds * energy_dot; v_el.CalcDShape(ip, dshape); - double JinvdJds_buffer[9]; + double JinvdJds_buffer[9] = {}; DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); - double dJds_buffer[9]; + double dJds_buffer[9] = {}; DenseMatrix dJds(dJds_buffer, space_dim, space_dim); MultAtB(dXds, dshape, dJds); Mult(trans.InverseJacobian(), dJds, JinvdJds); @@ -4107,14 +4107,14 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( b_mag_bar += energy_bar * energy_dot; /// double JinvdJdsTrace = JinvdJds.Trace(); - double JinvdJds_bar_buffer[9]; + double JinvdJds_bar_buffer[9] = {}; DenseMatrix JinvdJds_bar(JinvdJds_bar_buffer, space_dim, space_dim); JinvdJds_bar.Diag(JinvdJdsTrace_bar, space_dim); /// Mult(trans.InverseJacobian(), dJds, JinvdJds); - double dJds_bar_buffer[9]; + double dJds_bar_buffer[9] = {}; DenseMatrix dJds_bar(dJds_bar_buffer, space_dim, space_dim); - double inv_jac_bar_buffer[9]; + double inv_jac_bar_buffer[9] = {}; DenseMatrix inv_jac_bar(inv_jac_bar_buffer, space_dim, space_dim); MultABt(JinvdJds_bar, dJds, inv_jac_bar); MultAtB(trans.InverseJacobian(), JinvdJds_bar, dJds_bar); @@ -4122,9 +4122,9 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( /// Matrix inverse reverse mode rule: /// C = A^-1, /// A_bar = -C^T * C_bar * C^T - double scratch_buffer[9] = {0}; + double scratch_buffer[9] = {}; DenseMatrix scratch(scratch_buffer, space_dim, space_dim); - double jac_bar_buffer[9] = {0}; + double jac_bar_buffer[9] = {}; DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); jac_bar = 0.0; MultAtB(trans.InverseJacobian(), inv_jac_bar, scratch); @@ -4156,7 +4156,7 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( /// isotrans.JacobianRevDiff(db_magdJ, dBdX); /// aka AddMultABt(db_magdJ, dshape, dBdX); - double db_magdJ_bar_buffer[9] = {0}; + double db_magdJ_bar_buffer[9] = {}; DenseMatrix db_magdJ_bar(db_magdJ_bar_buffer, space_dim, space_dim); Mult(dBdX_bar, dshape, db_magdJ_bar); @@ -4169,19 +4169,19 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( double b_vec_norm_bar = 0.0; double trans_weight_bar = 0.0; - double adj_jac_bar_buffer[9] = {0}; + double adj_jac_bar_buffer[9] = {}; DenseMatrix adj_jac_bar(adj_jac_bar_buffer, space_dim, space_dim); if (dim == 3) { - double b_hat_buffer[3]; + double b_hat_buffer[3] = {}; Vector b_hat(b_hat_buffer, curl_dim); curlshape.MultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); - double BB_hatT_bar_buffer[9]; + double BB_hatT_bar_buffer[9] = {}; DenseMatrix BB_hatT_bar(BB_hatT_bar_buffer, curl_dim, curl_dim); BB_hatT_bar = 0.0; @@ -4218,7 +4218,7 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( } } - // double b_hat_bar_buffer[3]; + // double b_hat_bar_buffer[3] = {}; // Vector b_hat_bar(b_hat_bar_buffer, curl_dim); /// MultVWt(b_vec, b_hat, BB_hatT); @@ -4231,14 +4231,14 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( } else { - double b_adjJT_buffer[3]; + double b_adjJT_buffer[3] = {}; Vector b_adjJT(b_adjJT_buffer, curl_dim); trans.AdjugateJacobian().Mult(b_vec, b_adjJT); double a = -1 / (b_vec_norm * pow(trans_weight, 2)); /// AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); - double b_adjJT_var_buffer[3]; + double b_adjJT_var_buffer[3] = {}; Vector b_adjJT_bar(b_adjJT_var_buffer, curl_dim); b_vec_bar = 0.0; @@ -4352,10 +4352,10 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, curlshape_dFt.SetSize(ndof, curl_dim); PointMat_bar.SetSize(space_dim, v_el.GetDof()); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -4440,7 +4440,7 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; + double jac_bar_buffer[9] = {}; DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); jac_bar = 0.0; AddMult(curlshape_dFt_bar, curlshape, jac_bar); @@ -4453,7 +4453,7 @@ double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - double adj_bar_buffer[9]; + double adj_bar_buffer[9] = {}; DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); adj_bar = 0.0; MultAtB(curlshape, curlshape_dFt_bar, adj_bar); @@ -4522,10 +4522,10 @@ void ForceIntegrator2::AssembleElementVector(const FiniteElement &el, curlshape_dFt.SetSize(ndof, curl_dim); PointMat_bar.SetSize(space_dim, v_el.GetDof()); - double b_vec_buffer[3]; + double b_vec_buffer[3] = {}; Vector b_vec(b_vec_buffer, curl_dim); - double b_vec_bar_buffer[3]; + double b_vec_bar_buffer[3] = {}; Vector b_vec_bar(b_vec_bar_buffer, curl_dim); // cast the ElementTransformation @@ -4610,7 +4610,7 @@ void ForceIntegrator2::AssembleElementVector(const FiniteElement &el, MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; + double jac_bar_buffer[9] = {}; DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); jac_bar = 0.0; AddMult(curlshape_dFt_bar, curlshape, jac_bar); @@ -4623,7 +4623,7 @@ void ForceIntegrator2::AssembleElementVector(const FiniteElement &el, MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - double adj_bar_buffer[9]; + double adj_bar_buffer[9] = {}; DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); adj_bar = 0.0; MultAtB(curlshape, curlshape_dFt_bar, adj_bar); @@ -4759,14 +4759,14 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, const double b_mag = b_vec_norm / trans_weight; /// the following computes `\partial (||B||/|J|) / \partial J` - double dBmdJ_buffer[9]; + double dBmdJ_buffer[9] = {}; DenseMatrix dBmdJ(dBmdJ_buffer, curl_dim, curl_dim); dBmdJ = 0.0; if (dim == 3) { b_hat = 0.0; curlshape.AddMultTranspose(elfun, b_hat); - double BB_hatT_buffer[9]; + double BB_hatT_buffer[9] = {}; DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); MultVWt(b_vec, b_hat, BB_hatT); @@ -4802,9 +4802,9 @@ double ForceIntegrator::GetElementEnergy(const FiniteElement &el, auto force = dBds * energy_dot; v_el.CalcDShape(ip, dshape); - double JinvdJds_buffer[9]; + double JinvdJds_buffer[9] = {}; DenseMatrix JinvdJds(JinvdJds_buffer, space_dim, space_dim); - double dJds_buffer[9]; + double dJds_buffer[9] = {}; DenseMatrix dJds(dJds_buffer, space_dim, space_dim); MultAtB(dXds, dshape, dJds); Mult(trans.InverseJacobian(), dJds, JinvdJds); From 6a77ee9b1c9d1ef1f322e063006ee4c3bd5c01c7 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 14:06:31 -0600 Subject: [PATCH 25/72] removing ForceIntegrator2 (which was used to test a new implementation but didn't end up working) --- .../electromagnetics/electromag_integ.cpp | 354 ------------------ .../electromagnetics/electromag_integ.hpp | 52 --- test/unit/test_electromag_integ.cpp | 252 ------------- 3 files changed, 658 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 23c1b4ba..b6248ba4 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -4314,360 +4314,6 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( } } -double ForceIntegrator2::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) -{ - if (attrs.count(trans.Attribute) == 1) - { - return 0.0; - } - /// get the proper element, transformation, and v vector -#ifdef MFEM_THREAD_SAFE - Array vdofs; - Vector vfun; -#endif - int element = trans.ElementNo; - const auto &v_el = *v.FESpace()->GetFE(element); - v.FESpace()->GetElementVDofs(element, vdofs); - v.GetSubVector(vdofs, vfun); - DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); - if (vfun.Normlinf() < 1e-14) - { - return 0.0; - } - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); - int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim; - -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape; - DenseMatrix curlshape_dFt; - DenseMatrix curlshape_dFt_bar; - DenseMatrix PointMat_bar; -#endif - curlshape.SetSize(ndof, curl_dim); - curlshape_dFt.SetSize(ndof, curl_dim); - PointMat_bar.SetSize(space_dim, v_el.GetDof()); - - double b_vec_buffer[3] = {}; - Vector b_vec(b_vec_buffer, curl_dim); - - double b_vec_bar_buffer[3] = {}; - Vector b_vec_bar(b_vec_bar_buffer, curl_dim); - - // cast the ElementTransformation - auto &isotrans = dynamic_cast(trans); - - const IntegrationRule *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() - { - if (el.Space() == FunctionSpace::Pk) - { - return 2 * el.GetOrder() - 2; - } - else - { - return 2 * el.GetOrder(); - } - }(); - - ir = &IntRules.Get(el.GetGeomType(), order); - } - - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - - const double trans_weight = trans.Weight(); - /// holds quadrature weight - double w = ip.weight * trans_weight; - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else // Dealing with scalar H1 field representing Az - { - /// Not exactly the curl matrix, but since we just want the magnitude - /// of the curl it's okay - el.CalcDShape(ip, curlshape); - Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - } - - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans_weight; - - const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - // fun += energy * w; - double fun_bar = 1.0; - - double energy_bar = 0.0; - double w_bar = 0.0; - energy_bar += fun_bar * w; - w_bar += fun_bar * energy; - - /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - double b_mag_bar = 0.0; - const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); - b_mag_bar += energy_bar * energy_dot; - - double b_vec_norm_bar = 0.0; - double trans_weight_bar = 0.0; - /// const double b_mag = b_vec_norm / trans_weight; - b_vec_norm_bar += b_mag_bar / trans_weight; - trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); - - b_vec_bar = 0.0; - /// const double b_vec_norm = b_vec.Norml2(); - add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); - - PointMat_bar = 0.0; - if (dim == 3) - { - /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // transposed dimensions of curlshape_dFt - // so I don't have to transpose jac_bar later - curlshape_dFt_bar.SetSize(curl_dim, ndof); - MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); - - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9] = {}; - DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_bar); - isotrans.JacobianRevDiff(jac_bar, PointMat_bar); - } - else // Dealing with scalar H1 field representing Az - { - /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - curlshape_dFt_bar.SetSize(ndof, curl_dim); - MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); - - /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - double adj_bar_buffer[9] = {}; - DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); - adj_bar = 0.0; - MultAtB(curlshape, curlshape_dFt_bar, adj_bar); - isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); - } - - /// const double w = ip.weight * trans_weight; - trans_weight_bar += w_bar * ip.weight; - - isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); - - // code to insert PointMat_bar into mesh_coords_bar; - for (int j = 0; j < v_el.GetDof(); ++j) - { - for (int d = 0; d < space_dim; ++d) - { - fun -= dXds(j, d) * PointMat_bar(d, j); - } - } - } - return fun; -} - -void ForceIntegrator2::AssembleElementVector(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun, - Vector &elfun_bar) -{ - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); - int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim; - - elfun_bar.SetSize(ndof); - elfun_bar = 0.0; - if (attrs.count(trans.Attribute) == 1) - { - return; - } - - /// get the proper element, transformation, and v vector -#ifdef MFEM_THREAD_SAFE - Array vdofs; - Vector vfun; -#endif - int element = trans.ElementNo; - const auto &v_el = *v.FESpace()->GetFE(element); - v.FESpace()->GetElementVDofs(element, vdofs); - v.GetSubVector(vdofs, vfun); - DenseMatrix dXds(vfun.GetData(), v_el.GetDof(), v_el.GetDim()); - if (vfun.Normlinf() < 1e-14) - { - return; - } - -#ifdef MFEM_THREAD_SAFE - DenseMatrix dshape; - DenseMatrix curlshape; - DenseMatrix curlshape_dFt; - DenseMatrix curlshape_dFt_bar; - DenseMatrix PointMat_bar; -#endif - dshape.SetSize(v_el.GetDof(), space_dim); - curlshape.SetSize(ndof, curl_dim); - curlshape_dFt.SetSize(ndof, curl_dim); - PointMat_bar.SetSize(space_dim, v_el.GetDof()); - - double b_vec_buffer[3] = {}; - Vector b_vec(b_vec_buffer, curl_dim); - - double b_vec_bar_buffer[3] = {}; - Vector b_vec_bar(b_vec_bar_buffer, curl_dim); - - // cast the ElementTransformation - auto &isotrans = dynamic_cast(trans); - - const IntegrationRule *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() - { - if (el.Space() == FunctionSpace::Pk) - { - return 2 * el.GetOrder() - 2; - } - else - { - return 2 * el.GetOrder(); - } - }(); - - ir = &IntRules.Get(el.GetGeomType(), order); - } - - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - - double trans_weight = trans.Weight(); - - /// holds quadrature weight - const double w = ip.weight * trans_weight; - - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcDShape(ip, curlshape); - Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - } - - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - - const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans_weight; - - const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - // energy_fun += energy * w; - double energy_fun_bar = 1.0; - - double energy_bar = 0.0; - double w_bar = 0.0; - energy_bar += energy_fun_bar * w; - w_bar += energy_fun_bar * energy; - - /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); - double b_mag_bar = 0.0; - const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); - b_mag_bar += energy_bar * energy_dot; - - double b_vec_norm_bar = 0.0; - double trans_weight_bar = 0.0; - /// const double b_mag = b_vec_norm / trans_weight; - b_vec_norm_bar += b_mag_bar / trans_weight; - trans_weight_bar -= b_mag_bar * b_vec_norm / pow(trans_weight, 2); - - b_vec_bar = 0.0; - /// const double b_vec_norm = b_vec.Norml2(); - add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); - - PointMat_bar = 0.0; - if (dim == 3) - { - /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - // transposed dimensions of curlshape_dFt - // so I don't have to transpose jac_bar later - curlshape_dFt_bar.SetSize(curl_dim, ndof); - MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); - - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9] = {}; - DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_bar); - isotrans.JacobianRevDiff(jac_bar, PointMat_bar); - } - else // Dealing with scalar H1 field representing Az - { - /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - curlshape_dFt_bar.SetSize(ndof, curl_dim); - MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); - - /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); - double adj_bar_buffer[9] = {}; - DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); - adj_bar = 0.0; - MultAtB(curlshape, curlshape_dFt_bar, adj_bar); - isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); - } - - /// const double w = ip.weight * trans_weight; - trans_weight_bar += w_bar * ip.weight; - - isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); - - // for (int j = 0; j < v_el.GetDof(); ++j) - // { - // for (int d = 0; d < space_dim; ++d) - // { - // fun -= dXds(j, d) * PointMat_bar(d, j); - // } - // } - - /// start reverse pass... - double fun_bar = 1.0; - DenseMatrix PointMat_bar_bar(space_dim, v_el.GetDof()); - PointMat_bar_bar = 0.0; - // DenseMatrix dXds_bar(v_el.GetDof(), space_dim); - // dXds_bar = 0.0; - for (int j = 0; j < v_el.GetDof(); ++j) - { - for (int d = 0; d < space_dim; ++d) - { - /// fun -= dXds(j, d) * PointMat_bar(d, j); - PointMat_bar_bar(d, j) -= fun_bar * dXds(j, d); - // dXds_bar(j, d) -= fun_bar * PointMat_bar(d, j); // Don't need! - } - } - - /// isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); - /// - - // Weight(Matrix) -> scalar - // WeightRevDiff(scalar) -> Matrix - // WeightRevDiffRevDiff(Matrix) -> scalar ? - } -} - double ForceIntegrator::GetElementEnergy(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun) diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index f34bee3e..d40fda07 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1242,58 +1242,6 @@ class ForceIntegratorMeshSens3 : public mfem::LinearFormIntegrator #endif }; -class ForceIntegrator2 : public mfem::NonlinearFormIntegrator -{ -public: - ForceIntegrator2(StateCoefficient &nu, mfem::GridFunction &v) : nu(nu), v(v) - { } - - /// \brief - Compute forces/torques based on the virtual work method - /// \param[in] nu - model describing reluctivity - /// \param[in] v - the grid function containing virtual displacements for - /// each mesh node - /// \param[in] attrs - the regions the force is acting on - ForceIntegrator2(StateCoefficient &nu, - mfem::GridFunction &v, - std::unordered_set attrs) - : nu(nu), v(v), attrs(std::move(attrs)) - { } - - /// \brief - Compute element contribution to global force/torque - /// \param[in] el - the finite element - /// \param[in] trans - defines the reference to physical element mapping - /// \param[in] elfun - state vector of the element - /// \returns the element contribution to global force/torque - double GetElementEnergy(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun) override; - - /// \brief - Computes dJdu, for solving for the adjoint - /// \param[in] el - the finite element - /// \param[in] trans - defines the reference to physical element mapping - /// \param[in] elfun - state vector of the element - /// \param[out] elfun_bar - \partial J \partial u for this functional - void AssembleElementVector(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun, - mfem::Vector &elfun_bar) override; - -private: - /// material dependent model describing reluctivity - StateCoefficient ν - /// grid function containing virtual displacements for each mesh node - mfem::GridFunction &v; - /// set of attributes the force is acting on - std::unordered_set attrs; -#ifndef MFEM_THREAD_SAFE - mfem::DenseMatrix dshape; - mfem::DenseMatrix curlshape, curlshape_dFt, curlshape_dFt_bar; - mfem::DenseMatrix PointMat_bar; - mfem::Array vdofs; - mfem::Vector vfun; -#endif -}; - /// Functional integrator to compute forces/torques based on the virtual work /// method class ForceIntegrator : public mfem::NonlinearFormIntegrator diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 0baadf09..759e9db7 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -2313,258 +2313,6 @@ TEST_CASE("ForceIntegratorMeshSens3::AssembleRHSElementVect - 2D") } } -TEST_CASE("ForceIntegrator2::GetElementEnergy") -{ - using namespace mfem; - using namespace electromag_data; - - double delta = 1e-5; - - // generate a 8 element mesh - int num_edge = 2; - auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - Element::TETRAHEDRON); - mesh.EnsureNodes(); - const auto dim = mesh.SpaceDimension(); - - NonLinearCoefficient nu; - // LinearCoefficient nu; - - for (int p = 1; p <= 4; ++p) - { - DYNAMIC_SECTION("...for degree p = " << p) - { - // H1_FECollection fec(p, dim); - ND_FECollection fec(p, dim); - FiniteElementSpace fes(&mesh, &fec); - - // extract mesh nodes and get their finite-element space - auto &x_nodes = *mesh.GetNodes(); - auto &mesh_fes = *x_nodes.FESpace(); - - // create v displacement field - GridFunction v(&mesh_fes); - VectorFunctionCoefficient pert(dim, randVectorState); - v.ProjectCoefficient(pert); - - // initialize state; here we randomly perturb a constant state - GridFunction a(&fes); - a.ProjectCoefficient(pert); - - NonlinearForm functional(&fes); - functional.AddDomainIntegrator( - new mach::ForceIntegrator2(nu, v)); - - auto force = functional.GetEnergy(a); - - NonlinearForm energy(&fes); - energy.AddDomainIntegrator( - new mach::MagneticEnergyIntegrator(nu)); - - add(x_nodes, -delta, v, x_nodes); - auto dWds = -energy.GetEnergy(a); - add(x_nodes, 2*delta, v, x_nodes); - dWds += energy.GetEnergy(a); - dWds /= 2*delta; - - // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; - REQUIRE(force == Approx(-dWds)); - } - } -} - -TEST_CASE("ForceIntegrator2::GetElementEnergy - 2D") -{ - using namespace mfem; - using namespace electromag_data; - - double delta = 1e-5; - - // generate a 8 element mesh - int num_edge = 2; - auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, - Element::TRIANGLE); - mesh.EnsureNodes(); - const auto dim = mesh.SpaceDimension(); - - NonLinearCoefficient nu; - // LinearCoefficient nu; - - for (int p = 1; p <= 4; ++p) - { - DYNAMIC_SECTION("...for degree p = " << p) - { - H1_FECollection fec(p, dim); - // ND_FECollection fec(p, dim); - FiniteElementSpace fes(&mesh, &fec); - - // extract mesh nodes and get their finite-element space - auto &x_nodes = *mesh.GetNodes(); - auto &mesh_fes = *x_nodes.FESpace(); - - // create v displacement field - GridFunction v(&mesh_fes); - VectorFunctionCoefficient pert(dim, randVectorState); - v.ProjectCoefficient(pert); - - // initialize state; here we randomly perturb a constant state - GridFunction a(&fes); - FunctionCoefficient apert(randState); - a.ProjectCoefficient(apert); - // a.ProjectCoefficient(pert); - - NonlinearForm functional(&fes); - functional.AddDomainIntegrator( - new mach::ForceIntegrator2(nu, v)); - - auto force = functional.GetEnergy(a); - - NonlinearForm energy(&fes); - energy.AddDomainIntegrator( - new mach::MagneticEnergyIntegrator(nu)); - - add(x_nodes, -delta, v, x_nodes); - auto dWds = -energy.GetEnergy(a); - add(x_nodes, 2*delta, v, x_nodes); - dWds += energy.GetEnergy(a); - dWds /= 2*delta; - - // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; - REQUIRE(force == Approx(-dWds)); - } - } -} - -TEST_CASE("ForceIntegrator2::AssembleElementVector") -{ - using namespace mfem; - using namespace electromag_data; - - double delta = 1e-5; - - // generate a 8 element mesh - int num_edge = 2; - auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - Element::TETRAHEDRON); - mesh.EnsureNodes(); - const auto dim = mesh.SpaceDimension(); - - NonLinearCoefficient nu; - // LinearCoefficient nu; - - for (int p = 1; p <= 4; ++p) - { - DYNAMIC_SECTION("...for degree p = " << p) - { - ND_FECollection fec(p, dim); - FiniteElementSpace fes(&mesh, &fec); - - // extract mesh nodes and get their finite-element space - auto &x_nodes = *mesh.GetNodes(); - auto &mesh_fes = *x_nodes.FESpace(); - - // create v displacement field - GridFunction v(&mesh_fes); - VectorFunctionCoefficient pert(dim, randVectorState); - v.ProjectCoefficient(pert); - - // initialize state - GridFunction a(&fes); - a.ProjectCoefficient(pert); - - NonlinearForm functional(&fes); - functional.AddDomainIntegrator( - new mach::ForceIntegrator(nu, v)); - - // initialize the vector that dJdu multiplies - GridFunction p(&fes); - p.ProjectCoefficient(pert); - - // evaluate dJdu and compute its product with v - GridFunction dJdu(&fes); - functional.Mult(a, dJdu); - double dJdu_dot_p = InnerProduct(dJdu, p); - - // now compute the finite-difference approximation... - GridFunction q_pert(a); - q_pert.Add(-delta, p); - double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); - q_pert.Add(2 * delta, p); - dJdu_dot_p_fd += functional.GetEnergy(q_pert); - dJdu_dot_p_fd /= (2 * delta); - - REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); - } - } -} - -TEST_CASE("ForceIntegrator2::AssembleElementVector - 2D") -{ - using namespace mfem; - using namespace electromag_data; - - double delta = 1e-5; - - // generate a 8 element mesh - int num_edge = 2; - auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, - Element::TRIANGLE); - // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - // Element::TETRAHEDRON); - mesh.EnsureNodes(); - const auto dim = mesh.SpaceDimension(); - - NonLinearCoefficient nu; - // LinearCoefficient nu; - - for (int p = 1; p <= 4; ++p) - { - DYNAMIC_SECTION("...for degree p = " << p) - { - H1_FECollection fec(p, dim); - // ND_FECollection fec(p, dim); - FiniteElementSpace fes(&mesh, &fec); - - // extract mesh nodes and get their finite-element space - auto &x_nodes = *mesh.GetNodes(); - auto &mesh_fes = *x_nodes.FESpace(); - - // create v displacement field - GridFunction v(&mesh_fes); - VectorFunctionCoefficient v_pert(dim, randVectorState); - v.ProjectCoefficient(v_pert); - - // initialize state - GridFunction a(&fes); - FunctionCoefficient pert(randState); - a.ProjectCoefficient(pert); - - NonlinearForm functional(&fes); - functional.AddDomainIntegrator( - new mach::ForceIntegrator2(nu, v)); - - // initialize the vector that dJdu multiplies - GridFunction p(&fes); - p.ProjectCoefficient(pert); - - // evaluate dJdu and compute its product with v - GridFunction dJdu(&fes); - functional.Mult(a, dJdu); - double dJdu_dot_p = InnerProduct(dJdu, p); - - // now compute the finite-difference approximation... - GridFunction q_pert(a); - q_pert.Add(-delta, p); - double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); - q_pert.Add(2 * delta, p); - dJdu_dot_p_fd += functional.GetEnergy(q_pert); - dJdu_dot_p_fd /= (2 * delta); - - REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); - } - } -} - TEST_CASE("ForceIntegrator::GetElementEnergy") { using namespace mfem; From 407069a5a9b654e95a2a2baef6ed032ef2748a14 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 14:11:18 -0600 Subject: [PATCH 26/72] switch torque and force outputs to use new ForceIntegrator3 --- src/physics/electromagnetics/electromag_outputs.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 7c4eb5ca..765e2fa2 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -79,7 +79,8 @@ class ForceFunctional final auto &&attrs = options["attributes"].get>(); output.addOutputDomainIntegrator( - new ForceIntegrator(nu, fields.at("vforce").gridFunc(), attrs)); + new ForceIntegrator3(nu, fields.at("vforce").gridFunc(), attrs)); + // new ForceIntegrator(nu, fields.at("vforce").gridFunc(), attrs)); } private: @@ -153,7 +154,7 @@ class TorqueFunctional final { auto &&air_attrs = options["air_attributes"].get>(); output.addOutputDomainIntegrator( - new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs), + new ForceIntegrator3(nu, fields.at("vtorque").gridFunc(), attrs), // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), // attrs), air_attrs); @@ -161,7 +162,7 @@ class TorqueFunctional final else { output.addOutputDomainIntegrator( - new ForceIntegrator2(nu, fields.at("vtorque").gridFunc(), attrs)); + new ForceIntegrator3(nu, fields.at("vtorque").gridFunc(), attrs)); // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); } } From 2364c22d971664e4b4ec9501e95c61feae71f1e9 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 16 Aug 2022 14:12:01 -0600 Subject: [PATCH 27/72] make format --- src/physics/electromagnetics/electromag_outputs.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 765e2fa2..c9090f97 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -80,7 +80,7 @@ class ForceFunctional final auto &&attrs = options["attributes"].get>(); output.addOutputDomainIntegrator( new ForceIntegrator3(nu, fields.at("vforce").gridFunc(), attrs)); - // new ForceIntegrator(nu, fields.at("vforce").gridFunc(), attrs)); + // new ForceIntegrator(nu, fields.at("vforce").gridFunc(), attrs)); } private: From 6c13e05df1eb54ee844895be571f0ef7094f8f92 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Thu, 18 Aug 2022 11:01:51 -0600 Subject: [PATCH 28/72] updated L2CurlMagnitudeProjection to internally handle 2D cases, and make sure differentiation of the 2D cases works correctly --- src/utils/l2_transfer_operator.cpp | 135 ++++++++++------ test/unit/test_l2_transfer_operator.cpp | 207 ++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 49 deletions(-) diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index 2d4d1bc8..cea75487 100644 --- a/src/utils/l2_transfer_operator.cpp +++ b/src/utils/l2_transfer_operator.cpp @@ -572,12 +572,12 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation int state_dof = state_fe.GetDof(); int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + int curl_dim = space_dim; mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); - double curl_vec_buffer[3]; + double curl_vec_buffer[3] = {}; mfem::Vector curl_vec(curl_vec_buffer, curl_dim); const auto &ir = output_fe.GetNodes(); @@ -586,8 +586,16 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } curlshape_dFt.MultTranspose(el_state, curl_vec); const double curl_vec_norm = curl_vec.Norml2(); @@ -608,13 +616,13 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation int output_dof = output_fe.GetDof(); int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + int curl_dim = space_dim; mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); - mfem::Vector shape(output_dof); + mfem::Vector adj_shape(output_dof); - double curl_vec_buffer[3]; + double curl_vec_buffer[3] = {}; mfem::Vector curl_vec(curl_vec_buffer, curl_dim); const auto &ir = output_fe.GetNodes(); @@ -624,16 +632,24 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } curlshape_dFt.MultTranspose(el_state, curl_vec); const double curl_vec_norm = curl_vec.Norml2(); // const double curl_mag = curl_vec_norm / trans.Weight(); - output_fe.CalcPhysShape(trans, shape); + output_fe.CalcPhysShape(trans, adj_shape); - double output_adj = el_output_adj * shape; + double output_adj = el_output_adj * adj_shape; /// dummy functional for adjoint-weighted residual // double fun = output_adj * curl_mag; @@ -650,19 +666,18 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation double curl_vec_norm_bar = curl_mag_bar / trans.Weight(); /// const double curl_vec_norm = curl_vec.Norml2(); - double curl_vec_bar_buffer[3]; + double curl_vec_bar_buffer[3] = {}; mfem::Vector curl_vec_bar(curl_vec_bar_buffer, space_dim); - curl_vec_bar = 0.0; add(curl_vec_bar, curl_vec_norm_bar / curl_vec_norm, curl_vec, curl_vec_bar); /// only need state derivative - // double output_adj_vec_bar_buffer[3]; + // double output_adj_vec_bar_buffer[3] = {}; // mfem::Vector output_adj_vec_bar(output_adj_vec_bar_buffer, - // space_dim); output_adj_vec_bar = 0.0; add(output_adj_vec_bar, - // fun_bar, curl_vec, output_adj_vec_bar); + // space_dim); + // add(output_adj_vec_bar, fun_bar, curl_vec, output_adj_vec_bar); /// only need state derivative /// output_adj.MultTranspose(shape, output_adj_vec); @@ -672,10 +687,16 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation curlshape_dFt.AddMult(curl_vec_bar, el_state_bar); /// only need state derivative - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - - /// only need state derivative - /// state_fe.CalcVShape(trans, vshape); + /// if (space_dim == 3) + /// { + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + /// state_fe.CalcCurlShape(ip, curlshape); + /// } + /// else + /// { + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + /// state_fe.CalcDShape(ip, curlshape); + /// } } } void apply_mesh_coords_bar(const mfem::FiniteElement &state_fe, @@ -687,18 +708,19 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation { auto &isotrans = dynamic_cast(trans); auto &mesh_fe = *isotrans.GetFE(); - int space_dim = isotrans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; - int mesh_dof = mesh_fe.GetDof(); int state_dof = state_fe.GetDof(); int output_dof = output_fe.GetDof(); + int mesh_dof = mesh_fe.GetDof(); + + int space_dim = isotrans.GetSpaceDim(); + int curl_dim = space_dim; mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); mfem::Vector adj_shape(output_dof); - double curl_vec_buffer[3]; + double curl_vec_buffer[3] = {}; mfem::Vector curl_vec(curl_vec_buffer, curl_dim); mfem::Vector adj_shape_bar(output_dof); @@ -713,8 +735,16 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } curlshape_dFt.MultTranspose(el_state, curl_vec); const double curl_vec_norm = curl_vec.Norml2(); @@ -751,26 +781,43 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); /// const double curl_vec_norm = curl_vec.Norml2(); - double curl_vec_bar_buffer[3]; + double curl_vec_bar_buffer[3] = {}; mfem::Vector curl_vec_bar(curl_vec_bar_buffer, space_dim); - curl_vec_bar = 0.0; add(curl_vec_bar, curl_vec_norm_bar / curl_vec_norm, curl_vec, curl_vec_bar); - /// curlshape_dFt.MultTranspose(el_state, curl_vec); - curlshape_dFt_bar = 0.0; - AddMultVWt(curl_vec_bar, el_state, curlshape_dFt_bar); + if (space_dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, state_dof); + MultVWt(curl_vec_bar, el_state, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + mfem::DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + + /// state_fe.CalcCurlShape(ip, curlshape); + } + else + { + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt_bar.SetSize(state_dof, curl_dim); + MultVWt(el_state, curl_vec_bar, curlshape_dFt_bar); - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; - mfem::DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_bar); - isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9] = {}; + mfem::DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); - /// state_fe.CalcCurlShape(ip, curlshape); + /// state_fe.CalcDShape(ip, curlshape); + } /// insert PointMat_bar into mesh_coords_bar for (int j = 0; j < mesh_dof; ++j) @@ -882,17 +929,7 @@ L2CurlMagnitudeProjection::L2CurlMagnitudeProjection( : L2TransferOperator(state, mesh_coords, output, - [&state]() -> std::unique_ptr - { - if (state.mesh().SpaceDimension() == 3) - { - return std::make_unique(); - } - else - { - return std::make_unique(); - } - }()) + std::make_unique()) { } void L2TransferOperator::apply(const MachInputs &inputs, mfem::Vector &out_vec) diff --git a/test/unit/test_l2_transfer_operator.cpp b/test/unit/test_l2_transfer_operator.cpp index de06ee59..3f2a1e30 100644 --- a/test/unit/test_l2_transfer_operator.cpp +++ b/test/unit/test_l2_transfer_operator.cpp @@ -1040,6 +1040,107 @@ TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt state") REQUIRE(dout_dstate_v == Approx(dout_dstate_v_fd).margin(1e-8)); } +TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt state - 2D") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + int nxy = 4; + int nz = 1; + int num_edge = 2; + auto smesh = mfem::Mesh::MakeCartesian2D(num_edge, num_edge, + mfem::Element::TRIANGLE); + auto mesh = mfem::ParMesh(MPI_COMM_WORLD, smesh); + mesh.EnsureNodes(); + const auto dim = mesh.Dimension(); + + const auto p = 2; + + mach::FiniteElementState state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "H1"}}); + + mach::FiniteElementState dg_state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "DG"}}); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + + mach::FiniteElementState mesh_coords(mesh, *mesh_fespace); + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::L2CurlMagnitudeProjection op(state, mesh_coords, dg_state); + + mfem::Vector state_tv(state.space().GetTrueVSize()); + mfem::Vector dg_state_tv(dg_state.space().GetTrueVSize()); + + mfem::FunctionCoefficient state_coeff([&](const mfem::Vector &x) + { + return uniform_rand(gen); + }); + state.project(state_coeff, state_tv); + mfem::Vector state_tv_copy(state_tv); + + mfem::Vector out_bar(dg_state.space().GetTrueVSize()); + for (int i = 0; i < out_bar.Size(); ++i) + { + out_bar(i) = uniform_rand(gen); + } + mfem::Vector state_pert(state.space().GetTrueVSize()); + for (int i = 0; i < state_pert.Size(); ++i) + { + state_pert(i) = uniform_rand(gen); + } + + mfem::Vector state_bar(state.space().GetTrueVSize()); + state_bar = 0.0; + setInputs(op, {{"state", state_tv}}); + op.vectorJacobianProduct(out_bar, "state", state_bar); + + auto dout_dstate_v_local = state_pert * state_bar; + double dout_dstate_v; + MPI_Allreduce(&dout_dstate_v_local, + &dout_dstate_v, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + // now compute the finite-difference approximation... + auto delta = 1e-5; + double dout_dstate_v_fd_local = 0.0; + + add(state_tv, delta, state_pert, state_tv); + op.apply(state_tv, dg_state_tv); + dout_dstate_v_fd_local += out_bar * dg_state_tv; + + add(state_tv, -2*delta, state_pert, state_tv); + op.apply(state_tv, dg_state_tv); + dout_dstate_v_fd_local -= out_bar * dg_state_tv; + + dout_dstate_v_fd_local /= 2*delta; + double dout_dstate_v_fd; + MPI_Allreduce(&dout_dstate_v_fd_local, + &dout_dstate_v_fd, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + auto rank = state.space().GetMyRank(); + if (rank == 0) + { + std::cout << "dout_dstate_v: " << dout_dstate_v << "\n"; + std::cout << "dout_dstate_v_fd: " << dout_dstate_v_fd << "\n"; + } + + REQUIRE(dout_dstate_v == Approx(dout_dstate_v_fd).margin(1e-8)); +} + TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt mesh_coords") { std::default_random_engine gen; @@ -1151,3 +1252,109 @@ TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt mesh_coords") REQUIRE(dout_dmesh_v == Approx(dout_dmesh_v_fd).margin(1e-8)); } + +TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt mesh_coords - 2D") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + // generate a 8 element mesh + int num_edge = 2; + auto smesh = mfem::Mesh::MakeCartesian2D(num_edge, num_edge, + mfem::Element::TRIANGLE); + auto mesh = mfem::ParMesh(MPI_COMM_WORLD, smesh); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + const auto p = 4; + + /// create new state vector copying the mesh's fe space + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto &mesh_fespace = *mesh_gf.ParFESpace(); + mach::FiniteElementState mesh_coords(mesh, mesh_fespace, "mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::FiniteElementState state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "H1"}}); + + mach::FiniteElementState dg_state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "DG"}}); + + mach::L2CurlMagnitudeProjection op(state, mesh_coords, dg_state); + + mfem::Vector state_tv(state.space().GetTrueVSize()); + mfem::Vector dg_state_tv(dg_state.space().GetTrueVSize()); + + mfem::FunctionCoefficient state_coeff([&](const mfem::Vector &x) + { + return uniform_rand(gen); + }); + state.project(state_coeff, state_tv); + mfem::Vector state_tv_copy(state_tv); + + mfem::Vector out_bar(dg_state.space().GetTrueVSize()); + for (int i = 0; i < out_bar.Size(); ++i) + { + out_bar(i) = uniform_rand(gen); + } + mfem::Vector mesh_pert(mesh_coords.space().GetTrueVSize()); + for (int i = 0; i < mesh_pert.Size(); ++i) + { + mesh_pert(i) = uniform_rand(gen); + } + + mfem::Vector mesh_coords_bar(mesh_coords.space().GetTrueVSize()); + mesh_coords_bar = 0.0; + setInputs(op, {{"state", state_tv}}); + op.vectorJacobianProduct(out_bar, "mesh_coords", mesh_coords_bar); + + auto dout_dmesh_v_local = mesh_pert * mesh_coords_bar; + double dout_dmesh_v; + MPI_Allreduce(&dout_dmesh_v_local, + &dout_dmesh_v, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + // now compute the finite-difference approximation... + auto delta = 1e-5; + double dout_dmesh_v_fd_local = 0.0; + + add(mesh_coords_tv, delta, mesh_pert, mesh_coords_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); // update mesh nodes + op.apply(state_tv, dg_state_tv); + dout_dmesh_v_fd_local += out_bar * dg_state_tv; + + add(mesh_coords_tv, -2*delta, mesh_pert, mesh_coords_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); // update mesh nodes + op.apply(state_tv, dg_state_tv); + dout_dmesh_v_fd_local -= out_bar * dg_state_tv; + + dout_dmesh_v_fd_local /= 2*delta; + double dout_dmesh_v_fd; + MPI_Allreduce(&dout_dmesh_v_fd_local, + &dout_dmesh_v_fd, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + auto rank = state.space().GetMyRank(); + if (rank == 0) + { + std::cout << "dout_dmesh_v: " << dout_dmesh_v << "\n"; + std::cout << "dout_dmesh_v_fd: " << dout_dmesh_v_fd << "\n"; + } + + REQUIRE(dout_dmesh_v == Approx(dout_dmesh_v_fd).margin(1e-8)); +} \ No newline at end of file From 33f63aa9a8ce10f19c5440778728d213d5dc7b78 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 22 Aug 2022 12:43:38 -0600 Subject: [PATCH 29/72] Added RelaxedNewton method that uses a linesearch to improve convergence. Added LineSearch abstract class, with a BacktrackingLineSearch implementation. Added tests for RelaxedNewton using BacktrackingLineSearch on a simple 2D Rosenbrock minimization problem --- src/common/CMakeLists.txt | 4 + src/common/linesearch.cpp | 128 ++++++++++++++++++++++ src/common/linesearch.hpp | 63 +++++++++++ src/common/mfem_extensions.cpp | 9 +- src/common/relaxed_newton.cpp | 89 ++++++++++++++++ src/common/relaxed_newton.hpp | 30 ++++++ test/unit/CMakeLists.txt | 1 + test/unit/test_linesearch.cpp | 189 +++++++++++++++++++++++++++++++++ 8 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 src/common/linesearch.cpp create mode 100644 src/common/linesearch.hpp create mode 100644 src/common/relaxed_newton.cpp create mode 100644 src/common/relaxed_newton.hpp create mode 100644 test/unit/test_linesearch.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 43ba7ac7..a7e51d91 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -7,6 +7,7 @@ set(MACH_COMMON_HEADERS evolver.hpp functional_output.hpp inexact_newton.hpp + linesearch.hpp mach_linearform.hpp mach_output.hpp mach_residual.hpp @@ -15,6 +16,7 @@ set(MACH_COMMON_HEADERS mfem_extensions.hpp ode.hpp orthopoly.hpp + relaxed_newton.hpp sbp_fe.hpp surface.hpp surface_def.hpp @@ -28,12 +30,14 @@ target_sources(mach evolver.cpp functional_output.cpp inexact_newton.cpp + linesearch.cpp mach_linearform.cpp material_library.cpp matrix_operators.cpp mfem_extensions.cpp ode.cpp orthopoly.cpp + relaxed_newton.cpp sbp_fe.cpp ${MACH_COMMON_HEADERS} ) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp new file mode 100644 index 00000000..a64fb829 --- /dev/null +++ b/src/common/linesearch.cpp @@ -0,0 +1,128 @@ +#include + +#include "mfem.hpp" + +#include "linesearch.hpp" + +namespace +{ +/// create quadratic interpolant to phi(0), dphi(0), and phi(alpha) and return +/// its analytical minimum +double quadratic_interp(double phi0, + double dphi0, + double phi_alpha, + double alpha) +{ + auto c = phi0; + auto b = dphi0; + auto a = (phi_alpha - b * alpha - c) / (pow(alpha, 2)); + + return -b / (2 * a); +} + +/// create cubic interpolant to phi(0), dphi(0), phi(alpha1), and phi(alpha2) +/// and return its analytical minimum +double cubic_interp(double phi0, + double dphi0, + double phi_alpha1, + double alpha1, + double phi_alpha2, + double alpha2) +{ + auto denom = pow(alpha1, 2) * pow(alpha2, 2) * (alpha2 - alpha1); + auto a = (pow(alpha1, 2) * (phi_alpha2 - phi0 - dphi0 * alpha2) - + pow(alpha2, 2) * (phi_alpha1 - phi0 - dphi0 * alpha1)) / + denom; + + auto b = (-pow(alpha1, 3) * (phi_alpha2 - phi0 - dphi0 * alpha2) + + pow(alpha2, 3) * (phi_alpha1 - phi0 - dphi0 * alpha1)) / + denom; + + if (abs(a) < std::numeric_limits::epsilon()) + { + return dphi0 / (2 * b); + } + else + { + // discriminant + auto d = std::max(pow(b, 2) - 3 * a * dphi0, 0.0); + // quadratic equation root + return (-b + sqrt(d)) / (3 * a); + } +} + +} // namespace + +namespace mach +{ +double BacktrackingLineSearch::search(const std::function &phi, + double phi0, + double dphi0, + double alpha, + int max_iter) +{ + auto alpha1 = alpha; + auto alpha2 = alpha; + auto phi2 = phi(alpha2); + auto phi1 = phi2; + + int iter = 0; + std::cout << "linesearch iter 0: phi(0) = " << phi0 << ", dphi(0)/dalpha = " << dphi0 << "\n"; + while (phi2 > phi0 + mu * alpha2 * dphi0) + { + iter += 1; + std::cout << "linesearch iter "< max_iter) + { + std::cout << "Max iterations reached!\n"; + return alpha2; + } + + double alpha_tmp = 0.0; + if (iter == 1 || interp_order == 2) + { + alpha_tmp = quadratic_interp(phi0, dphi0, phi2, alpha2); + } + else + { + alpha_tmp = cubic_interp(phi0, dphi0, phi1, alpha1, phi2, alpha2); + } + alpha1 = alpha2; + phi1 = phi2; + + alpha_tmp = std::min(alpha_tmp, alpha2 * rho_hi); + alpha2 = std::max(alpha_tmp, alpha2 * rho_lo); + + phi2 = phi(alpha2); + } + std::cout << "Solved backtrack linesearch (alpha_star = " << alpha2 << ", phi(alpha_star) = "< + &calcRes, + const mfem::Vector &state, + const mfem::Vector &descent_dir, + mfem::Vector &residual, + mfem::Operator &jac) + : phi0(residual.Norml2()), + calcRes(calcRes), + state(state), + descent_dir(descent_dir), + scratch(state.Size()), + residual(residual) +{ + jac.Mult(descent_dir, scratch); + dphi0 = -(scratch * residual) / phi0; +} + +double Phi::operator()(double alpha) +{ + scratch = 0.0; + add(state, -alpha, descent_dir, scratch); + + calcRes(scratch, residual); + return residual.Norml2(); +} + +} // namespace mach diff --git a/src/common/linesearch.hpp b/src/common/linesearch.hpp new file mode 100644 index 00000000..d78afbbd --- /dev/null +++ b/src/common/linesearch.hpp @@ -0,0 +1,63 @@ +#ifndef MACH_LINESEARCH +#define MACH_LINESEARCH + +#include + +#include "mfem.hpp" + +namespace mach +{ +class LineSearch +{ +public: + virtual double search(const std::function &phi, + double phi0, + double dphi0, + double alpha, + int max_iter = 10) = 0; + + virtual ~LineSearch() = default; +}; + +class BacktrackingLineSearch : public LineSearch +{ +public: + double search(const std::function &phi, + double phi0, + double dphi0, + double alpha, + int max_iter) override; + + double mu = 1e-4; + double rho_hi = 0.9; + double rho_lo = 0.1; + int interp_order = 3; +}; + +/// Functor class for \phi, the 1D function linesearch methods try to minimize +class Phi +{ +public: + Phi(const std::function + &calcRes, + const mfem::Vector &state, + const mfem::Vector &descent_dir, + mfem::Vector &residual, + mfem::Operator &jac); + + double operator()(double alpha); + + double phi0; + double dphi0; + +private: + const std::function &calcRes; + const mfem::Vector &state; + const mfem::Vector &descent_dir; + mfem::Vector scratch; + mfem::Vector &residual; +}; + +} // namespace mach + +#endif diff --git a/src/common/mfem_extensions.cpp b/src/common/mfem_extensions.cpp index b16ba750..1ca28718 100644 --- a/src/common/mfem_extensions.cpp +++ b/src/common/mfem_extensions.cpp @@ -1,10 +1,13 @@ #include +#include #include "mfem.hpp" #include "evolver.hpp" -#include "utils.hpp" #include "matrix_operators.hpp" +#include "relaxed_newton.hpp" +#include "utils.hpp" + #include "mfem_extensions.hpp" using namespace mfem; @@ -545,6 +548,10 @@ std::unique_ptr constructNonlinearSolver( double gamma = nonlin_options.value("gamma", 1.0); nonlin_solver->SetAdaptiveLinRtol(type, rtol0, rtol_max, alpha, gamma); } + else if (solver_type == "relaxednewton") + { + nonlin_solver = std::make_unique(comm, nonlin_options); + } else { throw MachException( diff --git a/src/common/relaxed_newton.cpp b/src/common/relaxed_newton.cpp new file mode 100644 index 00000000..9921866a --- /dev/null +++ b/src/common/relaxed_newton.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "mfem.hpp" +#include "nlohmann/json.hpp" + +#include "linesearch.hpp" + +#include "relaxed_newton.hpp" +#include "utils.hpp" + +namespace +{ +void calcResidual(const mfem::Operator &oper, + const mfem::Vector &x, + const mfem::Vector &b, + mfem::Vector &res) +{ + const bool have_b = (b.Size() == oper.Height()); + oper.Mult(x, res); + if (have_b) + { + res -= b; + } +} + +std::unique_ptr createLineSearch(const std::string &type, + const nlohmann::json &options) +{ + if (type == "backtracking") + { + auto ls = std::make_unique(); + if (options.is_object()) + { + ls->mu = options.value("mu", ls->mu); + ls->rho_hi = options.value("rhohi", ls->rho_hi); + ls->rho_lo = options.value("rholo", ls->rho_lo); + ls->interp_order = options.value("interp-order", ls->interp_order); + } + return ls; + } + else + { + std::string err_msg = "Unknown linesearch type \""; + err_msg += type; + err_msg += "\"!\n"; + throw mach::MachException(err_msg); + } +} + +} // namespace + +namespace mach +{ +RelaxedNewton::RelaxedNewton(MPI_Comm comm, const nlohmann::json &options) + : NewtonSolver(comm) +{ + if (options.contains("linesearch")) + { + const auto &ls_opts = options["linesearch"]; + if (ls_opts.is_string()) + { + auto ls_type = ls_opts.get(); + ls = createLineSearch(ls_type, {}); + } + else + { + auto ls_type = ls_opts["type"].get(); + ls = createLineSearch(ls_type, ls_opts); + } + } + else + { + ls = std::make_unique(); + } +} + +double RelaxedNewton::ComputeScalingFactor(const mfem::Vector &x, + const mfem::Vector &b) const +{ + auto calcRes = [&](const mfem::Vector &x, mfem::Vector &res) + { calcResidual(*oper, x, b, res); }; + + auto phi = Phi(calcRes, x, c, r, *grad); + + return ls->search(phi, phi.phi0, phi.dphi0, 1.0); +} + +} // namespace mach diff --git a/src/common/relaxed_newton.hpp b/src/common/relaxed_newton.hpp new file mode 100644 index 00000000..9a1c53b8 --- /dev/null +++ b/src/common/relaxed_newton.hpp @@ -0,0 +1,30 @@ +#ifndef MACH_RELAXED_NEWTON +#define MACH_RELAXED_NEWTON + +#include + +#include "mfem.hpp" +#include "nlohmann/json.hpp" + +#include "linesearch.hpp" + +namespace mach +{ +/// Newton's method for solving F(x) = b augmented with a linesearch +class RelaxedNewton : public mfem::NewtonSolver +{ +public: + RelaxedNewton(MPI_Comm comm, const nlohmann::json &options); + + double ComputeScalingFactor(const mfem::Vector &x, + const mfem::Vector &b) const override; + +private: + mutable mfem::Vector scratch; + + std::unique_ptr ls; +}; + +} // namespace mach + +#endif diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index aeeecda7..d68a25ab 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -71,6 +71,7 @@ set(EM_MPI_TEST_SRCS test_pde_solver test_data_logging test_l2_transfer_operator + test_linesearch test_mesh_warper test_mach_nonlinearform ) diff --git a/test/unit/test_linesearch.cpp b/test/unit/test_linesearch.cpp new file mode 100644 index 00000000..16f546f5 --- /dev/null +++ b/test/unit/test_linesearch.cpp @@ -0,0 +1,189 @@ +#include + +#include "catch.hpp" +#include "mfem.hpp" + +#include "linesearch.hpp" +#include "relaxed_newton.hpp" + +namespace +{ +class FunctionalOperator : public mfem::Operator +{ +public: + FunctionalOperator(std::function calcRes, + std::function calcJac) + : Operator(2), calcRes(std::move(calcRes)), calcJac(std::move(calcJac)) + {} + + void Mult(const mfem::Vector &x, mfem::Vector &y) const override + { + calcRes(x, y); + } + + Operator & GetGradient(const mfem::Vector &x) const override + { + jac.SetSize(x.Size()); + calcJac(x, jac); + return jac; + } + +private: + std::function calcRes; + std::function calcJac; + mutable mfem::DenseMatrix jac; +}; + +std::default_random_engine gen; +std::uniform_real_distribution uniform_rand(-1.0,1.0); + +/// Gradient for Rosenbrock function: +/// f = 100*(x_1 - x_0^2)^2 + (1-x_0)^2 +auto calcRes = [](const mfem::Vector &x, mfem::Vector &res) +{ + res(0) = -400*x(0)*(x(1)-pow(x(0),2)) - 2*(1-x(0)); + res(1) = 200*(x(1)-pow(x(0),2)); +}; + +/// Hessian for Rosenbrock function: +/// f = 100*(x_1 - x_0^2)^2 + (1-x_0)^2 +auto calcJac = [](const mfem::Vector &x, mfem::DenseMatrix &jac) +{ + jac(0,0) = 1200*pow(x(0), 2) - 400*x(1) + 2; + jac(0,1) = -400*x(0); + jac(1,0) = -400*x(0); + jac(1,1) = 200; +}; + +} // anonymous namespace + +TEST_CASE("Rosenbrock calcJac") +{ + mfem::Vector state(2); + state(0) = uniform_rand(gen); + state(1) = uniform_rand(gen); + + mfem::DenseMatrix jac(2); + calcJac(state, jac); + + const double delta = 1e-5; + + mfem::Vector residual0p(2); + state(0) += delta; + calcRes(state, residual0p); + + mfem::Vector residual0m(2); + state(0) -= 2*delta; + calcRes(state, residual0m); + state(0) += delta; + + mfem::Vector residual1p(2); + state(1) += delta; + calcRes(state, residual1p); + + mfem::Vector residual1m(2); + state(1) -= 2*delta; + calcRes(state, residual1m); + state(1) += delta; + + mfem::DenseMatrix jac_fd(2); + jac_fd(0,0) = (residual0p(0) - residual0m(0)) / (2*delta); + jac_fd(0,1) = (residual0p(1) - residual0m(1)) / (2*delta); + jac_fd(1,0) = (residual1p(0) - residual1m(0)) / (2*delta); + jac_fd(1,1) = (residual1p(1) - residual1m(1)) / (2*delta); + + for (int i = 0; i < 2; ++i) + { + for (int j = 0; j < 2; ++j) + { + REQUIRE(jac(i,j) == Approx(jac_fd(i,j))); + } + } +} + +TEST_CASE("Phi::dphi0") +{ + mfem::Vector state(2); + state(0) = uniform_rand(gen); + state(1) = uniform_rand(gen); + + mfem::Vector residual(2); + calcRes(state, residual); + + mfem::DenseMatrix jac(2); + calcJac(state, jac); + + mfem::Vector descent_dir(2); + // descent_dir(0) = uniform_rand(gen); + // descent_dir(1) = uniform_rand(gen); + + /// If the descent direction is the result of a Newton step, dphi(0) = -phi(0) + mfem::DenseMatrix jac_inv(2); + CalcInverse(jac, jac_inv); + jac_inv.Mult(residual, descent_dir); + + auto phi = mach::Phi(calcRes, state, descent_dir, residual, jac); + double phi0 = phi.phi0; + double dphi0 = phi.dphi0; + + const double delta = 1e-7; + double phip = phi(delta); + double dphi0_fd = (phip - phi0)/delta; + REQUIRE(dphi0 == Approx(dphi0_fd)); + std::cout.precision(20); + std::cout << "phi0 = " << phi0 << "\n"; + std::cout << "dphi0 = " << dphi0 << "\n"; + std::cout << "dphi0_fd = " << dphi0_fd << "\n"; +} + +TEST_CASE("RelaxedNewton with BacktrackingLineSearch") +{ + FunctionalOperator oper(calcRes, calcJac); + + auto newton_opts = R"( + { + "linesearch": { + "type": "backtracking", + "mu": 1e-4, + "rhohi": 0.9, + "rholo": 0.1, + "interp-order": 3 + } + })"_json; + + mach::RelaxedNewton newton(MPI_COMM_SELF, newton_opts); + // mfem::NewtonSolver newton(MPI_COMM_SELF); + // newton.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); + newton.SetMaxIter(500); + newton.SetAbsTol(1e-6); + newton.SetRelTol(1e-6); + newton.SetOperator(oper); + + mfem::GMRESSolver gmres(MPI_COMM_SELF); + // gmres.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); + newton.SetSolver(gmres); + + mfem::Vector zero; + mfem::Vector state(2); + state(0) = 1.2; + state(1) = 1.2; + + newton.Mult(zero, state); + + REQUIRE(newton.GetFinalNorm() == Approx(0.00010890852988421259)); + REQUIRE(newton.GetNumIterations() == 59); + REQUIRE(state(0) == Approx(1.00001197)); + REQUIRE(state(1) == Approx(1.00002375)); + + state(0) = -1.2; + state(1) = 1.0; + + newton.Mult(zero, state); + + REQUIRE(newton.GetFinalNorm() == Approx(1.4199943164140142e-05)); + REQUIRE(newton.GetNumIterations() == 244); + REQUIRE(state(0) == Approx(0.99999982)); + REQUIRE(state(1) == Approx(0.99999961)); +} \ No newline at end of file From fc0ba8593b44080ef76eb62ac4844d54e2202b1a Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 22 Aug 2022 12:44:15 -0600 Subject: [PATCH 30/72] make format --- src/common/linesearch.cpp | 10 +++++++--- src/common/relaxed_newton.cpp | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp index a64fb829..6fcd0f1c 100644 --- a/src/common/linesearch.cpp +++ b/src/common/linesearch.cpp @@ -67,11 +67,13 @@ double BacktrackingLineSearch::search(const std::function &phi, auto phi1 = phi2; int iter = 0; - std::cout << "linesearch iter 0: phi(0) = " << phi0 << ", dphi(0)/dalpha = " << dphi0 << "\n"; + std::cout << "linesearch iter 0: phi(0) = " << phi0 + << ", dphi(0)/dalpha = " << dphi0 << "\n"; while (phi2 > phi0 + mu * alpha2 * dphi0) { iter += 1; - std::cout << "linesearch iter "< max_iter) { std::cout << "Max iterations reached!\n"; @@ -95,7 +97,9 @@ double BacktrackingLineSearch::search(const std::function &phi, phi2 = phi(alpha2); } - std::cout << "Solved backtrack linesearch (alpha_star = " << alpha2 << ", phi(alpha_star) = "< createLineSearch(const std::string &type, - const nlohmann::json &options) +std::unique_ptr createLineSearch( + const std::string &type, + const nlohmann::json &options) { if (type == "backtracking") { From c80e58c2bb5c53df6094adc9a519f219f9166134 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 22 Aug 2022 14:19:24 -0600 Subject: [PATCH 31/72] rework material library and reluctivity coefficients. We can now specify material data in the options dictionary, and handle multiple different nonlinear reluctivity models at once. --- src/common/coefficient.cpp | 400 +++---- src/common/coefficient.hpp | 72 -- src/common/material_library.cpp | 1031 +++++++++++------ .../reluctivity_coefficient.cpp | 590 +++++++++- src/physics/pde_solver.cpp | 19 + test/regression/test_magnetostatic_box.cpp | 20 +- test/regression/test_magnetostatic_box2d.cpp | 20 +- test/regression/test_thermal_cube.cpp | 14 +- test/unit/test_coefficient.cpp | 194 +++- test/unit/test_magnetic_load.cpp | 18 +- 10 files changed, 1655 insertions(+), 723 deletions(-) diff --git a/src/common/coefficient.cpp b/src/common/coefficient.cpp index feeeca4b..37b41c7d 100644 --- a/src/common/coefficient.cpp +++ b/src/common/coefficient.cpp @@ -206,8 +206,18 @@ std::unique_ptr constructMaterialCoefficient( { int attr = component.value("attr", -1); - const auto &material = component["material"].get(); - double val = materials[material].value(name, default_val); + const auto &material = component["material"]; + std::string material_name; + if (material.is_string()) + { + material_name = material.get(); + } + else + { + material_name = material["name"].get(); + } + + double val = materials[material_name].value(name, default_val); if (-1 != attr) { @@ -226,230 +236,168 @@ std::unique_ptr constructMaterialCoefficient( return material_coeff; } -NonlinearReluctivityCoefficient::NonlinearReluctivityCoefficient( - const std::vector &B, - const std::vector &H) - // : b_max(B[B.size()-1]), nu(H.size(), 1, 3) - : b_max(B[B.size() - 1]), - h_max(H[H.size() - 1]), - bh(std::make_unique(H.size(), 1, 3)) -{ - std::vector knots(B); - for (int i = 0; i < B.size(); ++i) - { - knots[i] = knots[i] / b_max; - } - bh->setControlPoints(H); - bh->setKnots(knots); - - dbdh = std::make_unique(bh->derive()); - // dnudb = nu.derive(); -} - -double NonlinearReluctivityCoefficient::Eval(ElementTransformation &trans, - const IntegrationPoint &ip, - const double state) -{ - constexpr double nu0 = 1 / (4e-7 * M_PI); - // std::cout << "eval state state: " << state << "\n"; - if (state <= 1e-14) - { - double t = state / b_max; - double nu = dbdh->eval(t).result()[0] / b_max; - return nu; - } - else if (state <= b_max) - { - double t = state / b_max; - double nu = bh->eval(t).result()[0] / state; - // std::cout << "eval state nu: " << nu << "\n"; - return nu; - } - else - { - return (h_max - nu0 * b_max) / state + nu0; - } -} - -double NonlinearReluctivityCoefficient::EvalStateDeriv( - ElementTransformation &trans, - const IntegrationPoint &ip, - const double state) -{ - constexpr double nu0 = 1 / (4e-7 * M_PI); - - /// TODO: handle state == 0 - if (state <= b_max) - { - double t = state / b_max; - double h = bh->eval(t).result()[0]; - return dbdh->eval(t).result()[0] / (state * b_max) - h / pow(state, 2); - } - else - { - return -(h_max - nu0 * b_max) / pow(state, 2); - } -} - -NonlinearReluctivityCoefficient::~NonlinearReluctivityCoefficient() = default; - -/// namespace for TEAM 13 B-H curve fit -namespace -{ -/** unused -double team13h(double b_hat) -{ - const double h = - exp((0.0011872363994136887 * pow(b_hat, 2) * (15 * pow(b_hat, 2) - 9.0) - - 0.19379133411847338 * pow(b_hat, 2) - - 0.012675319795245974 * b_hat * (3 * pow(b_hat, 2) - 1.0) + - 0.52650810858405916 * b_hat + 0.77170389255937188) / - (-0.037860246476916264 * pow(b_hat, 2) + - 0.085040155318288846 * b_hat + 0.1475250808150366)) - - 31; - return h; -} -*/ - -double team13dhdb_hat(double b_hat) -{ - const double dhdb_hat = - (-0.0013484718812450662 * pow(b_hat, 5) + - 0.0059829967461202211 * pow(b_hat, 4) + - 0.0040413617616232578 * pow(b_hat, 3) - - 0.013804440762666015 * pow(b_hat, 2) - 0.0018970139190370716 * b_hat + - 0.013917259962808418) * - exp((0.017808545991205332 * pow(b_hat, 4) - - 0.038025959385737926 * pow(b_hat, 3) - - 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat + - 0.77170389255937188) / - (-0.037860246476916264 * pow(b_hat, 2) + - 0.085040155318288846 * b_hat + 0.1475250808150366)) / - (0.0014333982632928504 * pow(b_hat, 4) - - 0.0064392824815713142 * pow(b_hat, 3) - - 0.0039388438258098624 * pow(b_hat, 2) + 0.025091111571707653 * b_hat + - 0.02176364946948308); - return dhdb_hat; -} - -double team13d2hdb_hat2(double b_hat) -{ - const double d2hdb_hat2 = - (1.8183764145086082e-6 * pow(b_hat, 10) - - 1.6135805755447689e-5 * pow(b_hat, 9) + - 2.2964027416433258e-5 * pow(b_hat, 8) + - 0.00010295509167249583 * pow(b_hat, 7) - - 0.0001721199302193437 * pow(b_hat, 6) - - 0.00031470749218644612 * pow(b_hat, 5) + - 0.00054873370082066282 * pow(b_hat, 4) + - 0.00078428896855240252 * pow(b_hat, 3) - - 0.00020176627749697931 * pow(b_hat, 2) - - 0.00054403666453702558 * b_hat - 0.00019679534359955033) * - exp((0.017808545991205332 * pow(b_hat, 4) - - 0.038025959385737926 * pow(b_hat, 3) - - 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat + - 0.77170389255937188) / - (-0.037860246476916264 * pow(b_hat, 2) + - 0.085040155318288846 * b_hat + 0.1475250808150366)) / - (2.0546305812109595e-6 * pow(b_hat, 8) - - 1.8460112651872795e-5 * pow(b_hat, 7) + - 3.0172495078875982e-5 * pow(b_hat, 6) + - 0.00012265776759231136 * pow(b_hat, 5) - - 0.0002452310649846335 * pow(b_hat, 4) - - 0.00047794451332165656 * pow(b_hat, 3) + - 0.00045811664722395466 * pow(b_hat, 2) + 0.001092148314092672 * b_hat + - 0.00047365643823053111); - return d2hdb_hat2; -} - -double team13b_hat(double b) -{ - const double b_hat = 1.10803324099723 * b + 1.10803324099723 * atan(20 * b) - - 0.9944598337950139; - return b_hat; -} - -double team13db_hatdb(double b) -{ - const double db_hatdb = (443.213296398892 * pow(b, 2) + 23.26869806094183) / - (400 * pow(b, 2) + 1); - return db_hatdb; -} - -double team13d2b_hatdb2(double b) -{ - const double d2b_hatdb2 = - -17728.53185595568 * b / pow(400 * pow(b, 2) + 1, 2); - return d2b_hatdb2; -} - -} // anonymous namespace - -double team13ReluctivityCoefficient::Eval(ElementTransformation &trans, - const IntegrationPoint &ip, - const double state) -{ - if (state > 2.2) - { - return 1 / (4 * M_PI * 1e-7); - } - const double b_hat = team13b_hat(state); - const double db_hatdb = team13db_hatdb(state); - - const double dhdb_hat = team13dhdb_hat(b_hat); - - const double nu = dhdb_hat * db_hatdb; - // std::cout << "state: " << state << " nu: " << nu << "\n"; - - // try - // { - // if (!isfinite(nu)) - // { - // throw MachException("nan!"); - // } - // } - // catch(const std::exception& e) - // { - // std::cerr << e.what() << '\n'; - // } - - return nu; -} - -double team13ReluctivityCoefficient::EvalStateDeriv( - ElementTransformation &trans, - const IntegrationPoint &ip, - const double state) -{ - if (state > 2.2) - { - return 0; - } - const double b_hat = team13b_hat(state); - const double db_hatdb = team13db_hatdb(state); - const double d2b_hatdb2 = team13d2b_hatdb2(state); - - const double dhdb_hat = team13dhdb_hat(b_hat); - const double d2hdb_hat2 = team13d2hdb_hat2(b_hat); - - // const double dnudb = d2hdb_hat2 * pow(db_hatdb, 2) + dhdb_hat * - // d2b_hatdb2; std::cout << "state: " << state << " dnudb: " << dnudb << - // "\n"; - - // try - // { - // if (!isfinite(dnudb)) - // { - // throw MachException("nan!"); - // } - // } - // catch(const std::exception& e) - // { - // std::cerr << e.what() << '\n'; - // } - - return d2hdb_hat2 * pow(db_hatdb, 2) + dhdb_hat * d2b_hatdb2; -} +// NonlinearReluctivityCoefficient::NonlinearReluctivityCoefficient( +// const std::vector &B, +// const std::vector &H) +// // : b_max(B[B.size()-1]), nu(H.size(), 1, 3) +// : b_max(B[B.size() - 1]), +// h_max(H[H.size() - 1]), +// bh(std::make_unique(H.size(), 1, 3)) +// { +// std::vector knots(B); +// for (int i = 0; i < B.size(); ++i) +// { +// knots[i] = knots[i] / b_max; +// } +// bh->setControlPoints(H); +// bh->setKnots(knots); + +// dbdh = std::make_unique(bh->derive()); +// // dnudb = nu.derive(); +// } + +// double NonlinearReluctivityCoefficient::Eval(ElementTransformation &trans, +// const IntegrationPoint &ip, +// const double state) +// { +// constexpr double nu0 = 1 / (4e-7 * M_PI); +// // std::cout << "eval state state: " << state << "\n"; +// if (state <= 1e-14) +// { +// double t = state / b_max; +// double nu = dbdh->eval(t).result()[0] / b_max; +// return nu; +// } +// else if (state <= b_max) +// { +// double t = state / b_max; +// double nu = bh->eval(t).result()[0] / state; +// // std::cout << "eval state nu: " << nu << "\n"; +// return nu; +// } +// else +// { +// return (h_max - nu0 * b_max) / state + nu0; +// } +// } + +// double NonlinearReluctivityCoefficient::EvalStateDeriv( +// ElementTransformation &trans, +// const IntegrationPoint &ip, +// const double state) +// { +// constexpr double nu0 = 1 / (4e-7 * M_PI); + +// /// TODO: handle state == 0 +// if (state <= b_max) +// { +// double t = state / b_max; +// double h = bh->eval(t).result()[0]; +// return dbdh->eval(t).result()[0] / (state * b_max) - h / pow(state, 2); +// } +// else +// { +// return -(h_max - nu0 * b_max) / pow(state, 2); +// } +// } + +// NonlinearReluctivityCoefficient::~NonlinearReluctivityCoefficient() = +// default; + +// /// namespace for TEAM 13 B-H curve fit +// namespace +// { +// /** unused +// double team13h(double b_hat) +// { +// const double h = +// exp((0.0011872363994136887 * pow(b_hat, 2) * (15 * pow(b_hat, 2) +// - 9.0) - +// 0.19379133411847338 * pow(b_hat, 2) - +// 0.012675319795245974 * b_hat * (3 * pow(b_hat, 2) - 1.0) + +// 0.52650810858405916 * b_hat + 0.77170389255937188) / +// (-0.037860246476916264 * pow(b_hat, 2) + +// 0.085040155318288846 * b_hat + 0.1475250808150366)) - +// 31; +// return h; +// } +// */ + +// double team13dhdb_hat(double b_hat) +// { +// const double dhdb_hat = +// (-0.0013484718812450662 * pow(b_hat, 5) + +// 0.0059829967461202211 * pow(b_hat, 4) + +// 0.0040413617616232578 * pow(b_hat, 3) - +// 0.013804440762666015 * pow(b_hat, 2) - 0.0018970139190370716 * b_hat +// + 0.013917259962808418) * +// exp((0.017808545991205332 * pow(b_hat, 4) - +// 0.038025959385737926 * pow(b_hat, 3) - +// 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat +// + 0.77170389255937188) / +// (-0.037860246476916264 * pow(b_hat, 2) + +// 0.085040155318288846 * b_hat + 0.1475250808150366)) / +// (0.0014333982632928504 * pow(b_hat, 4) - +// 0.0064392824815713142 * pow(b_hat, 3) - +// 0.0039388438258098624 * pow(b_hat, 2) + 0.025091111571707653 * b_hat +// + 0.02176364946948308); +// return dhdb_hat; +// } + +// double team13d2hdb_hat2(double b_hat) +// { +// const double d2hdb_hat2 = +// (1.8183764145086082e-6 * pow(b_hat, 10) - +// 1.6135805755447689e-5 * pow(b_hat, 9) + +// 2.2964027416433258e-5 * pow(b_hat, 8) + +// 0.00010295509167249583 * pow(b_hat, 7) - +// 0.0001721199302193437 * pow(b_hat, 6) - +// 0.00031470749218644612 * pow(b_hat, 5) + +// 0.00054873370082066282 * pow(b_hat, 4) + +// 0.00078428896855240252 * pow(b_hat, 3) - +// 0.00020176627749697931 * pow(b_hat, 2) - +// 0.00054403666453702558 * b_hat - 0.00019679534359955033) * +// exp((0.017808545991205332 * pow(b_hat, 4) - +// 0.038025959385737926 * pow(b_hat, 3) - +// 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat +// + 0.77170389255937188) / +// (-0.037860246476916264 * pow(b_hat, 2) + +// 0.085040155318288846 * b_hat + 0.1475250808150366)) / +// (2.0546305812109595e-6 * pow(b_hat, 8) - +// 1.8460112651872795e-5 * pow(b_hat, 7) + +// 3.0172495078875982e-5 * pow(b_hat, 6) + +// 0.00012265776759231136 * pow(b_hat, 5) - +// 0.0002452310649846335 * pow(b_hat, 4) - +// 0.00047794451332165656 * pow(b_hat, 3) + +// 0.00045811664722395466 * pow(b_hat, 2) + 0.001092148314092672 * b_hat +// + 0.00047365643823053111); +// return d2hdb_hat2; +// } + +// double team13b_hat(double b) +// { +// const double b_hat = 1.10803324099723 * b + 1.10803324099723 * atan(20 * +// b) - +// 0.9944598337950139; +// return b_hat; +// } + +// double team13db_hatdb(double b) +// { +// const double db_hatdb = (443.213296398892 * pow(b, 2) + 23.26869806094183) +// / +// (400 * pow(b, 2) + 1); +// return db_hatdb; +// } + +// double team13d2b_hatdb2(double b) +// { +// const double d2b_hatdb2 = +// -17728.53185595568 * b / pow(400 * pow(b, 2) + 1, 2); +// return d2b_hatdb2; +// } + +// } // anonymous namespace void VectorMeshDependentCoefficient::Eval(Vector &vec, ElementTransformation &trans, diff --git a/src/common/coefficient.hpp b/src/common/coefficient.hpp index 3486099e..4dbc3375 100644 --- a/src/common/coefficient.hpp +++ b/src/common/coefficient.hpp @@ -368,78 +368,6 @@ std::unique_ptr constructMaterialCoefficient( const nlohmann::json &materials, double default_val = 0.0); -class NonlinearReluctivityCoefficient : public StateCoefficient -{ -public: - /// \brief Define a reluctivity model from a B-Spline fit with linear - /// extrapolation at the far end - /// \param[in] B - magnetic flux density values from B-H curve - /// \param[in] H - magnetic field intensity valyes from B-H curve - NonlinearReluctivityCoefficient(const std::vector &B, - const std::vector &H); - - /// \brief Evaluate the reluctivity in the element described by trans at the - /// point ip. - /// \note When this method is called, the caller must make sure that the - /// IntegrationPoint associated with trans is the same as ip. This can be - /// achieved by calling trans.SetIntPoint(&ip). - double Eval(mfem::ElementTransformation &trans, - const mfem::IntegrationPoint &ip, - double state) override; - - /// \brief Evaluate the derivative of reluctivity with respsect to magnetic - /// flux in the element described by trans at the point ip. - /// \note When this method is called, the caller must make sure that the - /// IntegrationPoint associated with trans is the same as ip. This can be - /// achieved by calling trans.SetIntPoint(&ip). - double EvalStateDeriv(mfem::ElementTransformation &trans, - const mfem::IntegrationPoint &ip, - double state) override; - - void EvalRevDiff(const double Q_bar, - mfem::ElementTransformation &trans, - const mfem::IntegrationPoint &ip, - mfem::DenseMatrix &PointMat_bar) override - { } - - ~NonlinearReluctivityCoefficient(); - -protected: - /// max B value in the data - double b_max; - /// max H value in the data - double h_max; - /// spline representing H(B) - std::unique_ptr bh; - /// spline representing dH(B)/dB - std::unique_ptr dbdh; -}; - -class team13ReluctivityCoefficient : public StateCoefficient -{ -public: - /// \brief Define a reluctivity model for the team13 steel - team13ReluctivityCoefficient() { std::cout << "using team13 coeff!\n"; } - - /// \brief Evaluate the reluctivity in the element described by trans at the - /// point ip. - /// \note When this method is called, the caller must make sure that the - /// IntegrationPoint associated with trans is the same as ip. This can be - /// achieved by calling trans.SetIntPoint(&ip). - double Eval(mfem::ElementTransformation &trans, - const mfem::IntegrationPoint &ip, - double state) override; - - /// \brief Evaluate the derivative of reluctivity with respsect to magnetic - /// flux in the element described by trans at the point ip. - /// \note When this method is called, the caller must make sure that the - /// IntegrationPoint associated with trans is the same as ip. This can be - /// achieved by calling trans.SetIntPoint(&ip). - double EvalStateDeriv(mfem::ElementTransformation &trans, - const mfem::IntegrationPoint &ip, - double state) override; -}; - class VectorMeshDependentCoefficient : public mfem::VectorCoefficient { public: diff --git a/src/common/material_library.cpp b/src/common/material_library.cpp index cdb0dea8..3ef21bf5 100644 --- a/src/common/material_library.cpp +++ b/src/common/material_library.cpp @@ -4,353 +4,690 @@ namespace mach { /// Defines the material library for for mach -/// -/// This is placed in a hpp file instead of a json file so that it can be -/// compiled in and doesn't require a path to it. This also allows comments. -const nlohmann::json material_library{ - {"box1", - { - {"rho", 1}, - {"cv", 1}, - {"kappa", 1}, - {"kappae", 1}, - {"sigma", -1}, - {"kh", -0.00013333333333333}, - {"ke", -3.55555555555e-8}, - {"alpha", 1.0}, - {"max-temp", 0.5}, - // {"mu_r", 1.0} // for real scaled problem - {"mu_r", 1 / (4 * M_PI * 1e-7)} // for unit scaled problem - }}, - {"box2", - { - {"rho", 1}, - {"cv", 1}, - {"kappa", 1}, - {"kappae", 1}, - {"sigma", -1}, - {"kh", -0.00013333333333333}, - {"ke", -3.55555555555e-8}, - {"alpha", 1.0}, - {"max-temp", 0.5}, - // {"mu_r", 1.0} // for real scaled problem - {"mu_r", 1 / (4 * M_PI * 1e-7)} // for unit scaled problem - }}, - {"testmat", - { - {"mu_r", 1}, - {"B_r", 1.0}, - {"rho", 1}, - {"cv", 1}, - {"kappa", 1}, - {"kappae", 1}, - {"sigma", -1}, - {"kh", -0.00013333333333333}, - {"ke", -3.55555555555e-8}, - {"alpha", 1.0}, - }}, - {"regtestmat1", - { - {"mu_r", 1}, - {"rho", 1}, - {"cv", 1}, - {"kappa", 1}, - {"kappae", 1}, - {"sigma", -1}, - }}, - {"regtestmat2", - { - {"mu_r", 1}, - {"rho", 1}, - {"cv", 1}, - {"kappa", 1}, - {"kappae", 1}, - {"sigma", 1}, - }}, - {"ideal", - {{"mu_r", 1e8}, - {"rho", 8120.0}, - {"cv", 0.420}, - {"kappa", 20}, - // {"kh", 0.02}, - // {"ke", 0.0001}, - {"ks", 0.0044}, - {"beta", 1.76835}, - {"alpha", 1.286}}}, - {"hiperco50", - {{"B", - // {0.0, - // 0.0, - // 0.0, - // 0.0, - // 1.424590497285806, - // 1.879802197738017, - // 2.089177970766961, - // 2.184851867536579, - // 2.229448683, - // 2.264761102805212, - // 2.302883806114454, - // 7.290145827, - // 7.290145827, - // 7.290145827, - // 7.290145827}}, - {0., 0., 0., 0., 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, - 0.9, 1., 1.1, 1.1976, 1.3, 1.4, 1.4775, 1.6, 1.7119, 1.8, 1.9, - 2., 2.1, 2.1868, 2.25, 2.3, 2.36, 2.4, 2.5, 2.5, 2.5, 2.5}}, - {"H", - // {0.0, - // 8.993187962999999, - // 10.978287105, - // 83.247282676, - // 211.230768762, - // 458.336047457, - // 1159.131573849, - // 2773.494054824, - // 1.342303920594017e6, - // 2.675328620250853e6, - // 3.9982409587815357e6}}, - {-8.45567148e-16, 1.52187154e+01, 2.44069859e+01, 2.87543851e+01, - 3.28574240e+01, 3.53931188e+01, 3.83409008e+01, 4.10642780e+01, - 4.38435871e+01, 4.64801737e+01, 4.96317182e+01, 5.29859037e+01, - 5.66857479e+01, 6.15933673e+01, 6.64821406e+01, 7.75969027e+01, - 9.75330122e+01, 1.21434401e+02, 1.52150611e+02, 1.94912815e+02, - 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, - 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, - 3.97887360e+04}}, - {"mu_r", 500}, - {"rho", 8110.0}, - {"cv", 0.420}, - {"kappa", 20}, - // {"kh", 0.02}, - // {"ke", 0.0001}, - {"ks", 0.0044}, - {"beta", 1.76835}, - {"alpha", 1.286}}}, - {"ansys-steel", - { - {"mu_r", 10000}, - }}, - {"steel", - {{"B", {0.0000000000000000, 0.0044450000000000, 0.0058190000000000, - 0.0071930000000000, 0.0085680000000000, 0.0128980000000000, - 0.0172280000000000, 0.0274700000000000, 0.0465790000000000, - 0.0922889999999999, 0.2207600000000000, 0.4113000000000000, - 0.6018500000000000, 0.7717000000000000, 0.9149500000000000, - 1.0464000000000000, 1.1600999999999900, 1.2619000000000000, - 1.3431000000000000, 1.3947000000000000, 1.4286000000000000, - 1.4507000000000000, 1.4668000000000000, 1.4830000000000000, - 1.4932000000000000, 1.5064000000000000, 1.5166999999999900, - 1.5268999999999900, 1.5371999999999900, 1.5474000000000000, - 1.5576000000000000, 1.5679000000000000, 1.5840000000000000, - 1.5972000000000000, 1.6104000000000000, 1.6266000000000000, - 1.6427000000000000, 1.6589000000000000, 1.6779999999999900, - 1.6971000000000000, 1.7161999999999900, 1.7353000000000000, - 1.7604000000000000, 1.7854000000000000, 1.8104000000000000, - 1.8384000000000000, 1.8723000000000000, 1.9060999999999900, - 1.9459000000000000, 1.9886999999999900, 2.0344000000000000, - 2.0741999999999900, 2.1080999999999900, 2.1389999999999900, - 2.1610999999999900, 2.1772000000000000, 2.1903999999999900}}, - {"H", {0.0000000000000000, 9.5103069999999900, - 11.2124700000000000, 13.2194140000000000, - 15.5852530000000000, 18.3712620000000000, - 21.6562209999999000, 25.5213000000000000, - 30.0619920000000000, 35.3642410000000000, - 41.4304339999999000, 48.3863029999999000, - 56.5103700000000000, 66.0660359999999000, - 77.3405759999999000, 90.5910259999999000, - 106.2120890000000000, 124.5944920000000000, - 146.3111910000000000, 172.0624699999990000, - 202.5247369999990000, 238.5255980000000000, - 281.0120259999990000, 331.0583149999990000, - 390.1446090000000000, 459.6953439999990000, - 541.7317890000000000, 638.4104939999990000, - 752.3336430000000000, 886.5729270000000000, - 1044.7729970000000000, 1231.2230800000000000, - 1450.5386699999900000, 1709.1655450000000000, - 2013.8677920000000000, 2372.5235849999900000, - 2795.1596880000000000, 3292.9965269999900000, - 3878.9256599999900000, 4569.1013169999900000, - 5382.0650580000000000, 6339.7006929999900000, - 7465.5631620000000000, 8791.7222000000000000, - 10352.2369750000000000, 12188.8856750000000000, - 14347.8232499999000000, 16887.9370500000000000, - 19872.0933000000000000, 23380.6652749999000000, - 27504.3713250000000000, 32364.9650250000000000, - 38095.3407999999000000, 44847.4916749999000000, - 52819.5656250000000000, 62227.2176750000000000, - 73321.1169499999000000}}, - {"mu_r", 750}, - {"rho", 7.750}, - {"cv", 0.420}, - {"kappa", 50}, - // {"kh", 0.02}, - // {"ke", 0.0001}, - {"ks", 0.0044}, - {"beta", 1.76835}, - {"alpha", 1.286}}}, - {"team13", - { - {"B", - {// 0.0, 0.0, 0.0, 0.0, 0.1, - // 0.4672, 1.1862, 1.4124, 1.6386, 4.3078, 15.8322, 15.8322, - // 15.8322, 15.8322 0, 0.025, 0.3, - // 0.7, 1.1, 1.5, 1.7, 1.8 - 0.0, - 0.0, - 0.0, - 0.0, - 0.0979, - 1.3841, - 1.800000001641453, - 1.999999999999916, - 2.22, - 2.22, - 2.22, - 2.22}}, - {"H", - {// 8.768456660138328, 7.962589775712834, 6.836102567668674, - // 5.849726164223915, - // 5.817004866729157, 5.925084803960186, 12.847141274392365, - // 13.168983405092462, - // 13.587072660625301, 13.587072660625301 0.0, 93, - // 222, 272, 377, 933, 4993, 9423 - 0.0, - 164.46362274610001, - 301.3546318106, - 161.7681804384, - 961.7045565209, - 27015.6213073554, - 138424.0810362731, - 196780.8935033237}}, - {"mu_r", 750} - // {"B", - // { - // 0, - // 0.0025, - // 0.005, - // 0.0125, - // 0.025, - // 0.05, - // 0.1, - // 0.2, - // 0.3, - // 0.4, - // 0.5, - // 0.6, - // 0.7, - // 0.8, - // 0.9, - // 1.0, - // 1.1, - // 1.2, - // 1.3, - // 1.4, - // 1.5, - // 1.55, - // 1.6, - // 1.65, - // 1.7, - // 1.75, - // 1.8, - // 1.8435526351982585, - // 1.9179901552741234, - // 1.984275166575515, - // 2.042407669102433, - // 2.092387662854877, - // 2.1342151478328484, - // 2.1678901240363464, - // 2.1934125914653704, - // 2.210782550119921, - // 2.219999999999999, - // 2.222831853071796, - // 2.2605309649148735, - // 2.298230076757951, - // 2.3359291886010287, - // 2.373628300444106 - // }}, - // {"H", - // { - // 0.0, - // 16, - // 30, - // 54, - // 93, - // 143, - // 191, - // 210, - // 222, - // 233, - // 247, - // 258, - // 272, - // 289, - // 313, - // 342, - // 377, - // 433, - // 509, - // 648, - // 933, - // 1228, - // 1934, - // 2913, - // 4993, - // 7189, - // 9423, - // 11657.0, - // 15794.62323416157, - // 19932.24646832314, - // 24069.869702484713, - // 28207.492936646286, - // 32345.116170807854, - // 36482.73940496943, - // 40620.362639130995, - // 44757.98587329257, - // 48895.60910745414, - // 50000.0, - // 80000.0, - // 110000.0, - // 140000.0, - // 170000.0 - // }} - }}, - {"Nd2Fe14B", - {{"mu_r", 1.04}, - {"B_r", 1.390}, - // {"B_r", 0.0}, - {"rho", 7500}, - {"cv", 502.08}, - {"kappa", 9}, - {"max-temp", 310 + 273.15}, // Curie temp is 310 C - // {"ks", 18.092347463670936}, - {"ks", 500}, - {"beta", 0.0}, - {"alpha", 0.0}}}, - {"air", - {{"mu_r", 1}, - {"rho", 1.225}, - {"cv", 93}, - {"kappa", 0.026}, - {"max-temp", 500}}}, - {"copper", // this is meant for the heatsink - { - {"mu_r", 1}, - {"rho", 8960}, - {"cv", 376}, - {"kappa", 400} - // {"sigma", 58.14e6}, - }}, - {"2024-T3", // motor heatsink from Reference Paper - {{"mu_r", 1}, {"rho", 2780}, {"cv", 875}, {"kappa", 120}}}, - {"copperwire", // meant for windings - should have reduced - // conductivity - {{"mu_r", 1}, - {"rho", 8960}, - {"cv", 376}, - // {"kappa", 237.6}, - {"kappa", 2.49}, - {"sigma", 58.14e6}, - {"max-temp", 400}}}}; +const auto hiperco50 = R"( +{ + "reluctivity": { + "lognu": { + "cps": [ + 5.401, + 4.3732, + 3.9991, + 3.8783, + 3.8492, + 3.8882, + 4.3043, + 4.8375, + 10.251, + 13.5846, + 13.5848], + "degree": 2, + "knots": [ + 0, + 0, + 0, + 0.6209, + 0.9571, + 1.1516, + 1.346, + 1.5888, + 1.8193, + 2.3539, + 2.7175, + 3.5, + 3.5, + 3.5] + }, + "bh": { + "cps": [ + 0, + 15.2187154, + 24.4069859, + 28.7543851, + 32.857424, + 35.3931188, + 38.3409008, + 41.064278, + 43.8435871, + 46.4801737, + 49.6317182, + 52.9859037, + 56.6857479, + 61.5933673, + 66.4821406, + 77.5969027, + 97.5330122, + 121.434401, + 152.150611, + 194.912815, + 326.052424, + 316.221363, + 1174.92265, + 2375.59462, + 3701.26207, + 7186.75547, + 13535.5204, + 21724.8492, + 39788.736], + "degree": 3, + "knots": [ + 0, + 0, + 0, + 0, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1, + 1.1, + 1.1976, + 1.3, + 1.4, + 1.4775, + 1.6, + 1.7119, + 1.8, + 1.9, + 2, + 2.1, + 2.1868, + 2.25, + 2.3, + 2.36, + 2.4, + 2.5, + 2.5, + 2.5, + 2.5] + } + }, + "alpha": 1.286, + "beta": 1.76835, + "cv": 0.42, + "kappa": 20, + "ks": 0.0044, + "mu_r": 500, + "rho": 8110 +} +)"_json; + +const auto team13 = R"( +{ + "reluctivity": { + "bh": { + "cps": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0979, + 1.3841, + 1.800000001641453, + 1.999999999999916, + 2.22, + 2.22, + 2.22, + 2.22 + ], + "degree": 3, + "knots": [ + 0.0, + 164.46362274610001, + 301.3546318106, + 161.7681804384, + 961.7045565209, + 27015.6213073554, + 138424.0810362731, + 196780.8935033237 + ] + } + }, + "mu_r": 750 +} +)"_json; + +const auto Nd2Fe14B = R"( +{ + "mu_r": 1.04, + "B_r": 1.390, + "rho": 7500, + "cv": 502.08, + "kappa": 9, + "max-temp": 583.15, + "ks": 500, + "beta": 0.0, + "alpha": 0.0 +} +)"_json; +// {"ks", 18.092347463670936, +// "max-temp": 310 + 273.15, // Curie temp is 310 C + +const auto air = R"( +{ + "rho": 1.225, + "cv": 93, + "kappa": 0.026 +} +)"_json; + +// meant for windings (should have reduced conductivity) +const auto copper_wire = R"( +{ + "rho": 8960, + "cv": 376, + "kappa": 2.49, + "sigma": 58.14e6 +} +)"_json; + +// this is meant for the heatsink +const auto copper = R"( +{ + "rho": 8960, + "cv": 376, + "kappa": 400 +} +)"_json; + +// motor heatsink from X-57 Reference Paper +const auto Al2024_T3 = R"( +{ + "rho": 2780, + "cv": 875, + "kappa": 120 +} +)"_json; + +const nlohmann::json material_library = {{"hiperco50", hiperco50}, + {"team13", team13}, + {"Nd2Fe14B", Nd2Fe14B}, + {"air", air}, + {"copperwire", copper_wire}, + {"copper", copper}, + {"2024_T3", Al2024_T3}}; + +// const nlohmann::json material_library +// { +// {"box1", +// { +// {"rho", 1}, +// {"cv", 1}, +// {"kappa", 1}, +// {"kappae", 1}, +// {"sigma", -1}, +// {"kh", -0.00013333333333333}, +// {"ke", -3.55555555555e-8}, +// {"alpha", 1.0}, +// {"max-temp", 0.5}, +// // {"mu_r", 1.0} // for real scaled problem +// {"mu_r", 1 / (4 * M_PI * 1e-7)} // for unit scaled problem +// } +// }, +// {"box2", +// { +// {"rho", 1}, +// {"cv", 1}, +// {"kappa", 1}, +// {"kappae", 1}, +// {"sigma", -1}, +// {"kh", -0.00013333333333333}, +// {"ke", -3.55555555555e-8}, +// {"alpha", 1.0}, +// {"max-temp", 0.5}, +// // {"mu_r", 1.0} // for real scaled problem +// {"mu_r", 1 / (4 * M_PI * 1e-7)} // for unit scaled problem +// } +// }, +// {"testmat", +// { +// {"mu_r", 1}, +// {"B_r", 1.0}, +// {"rho", 1}, +// {"cv", 1}, +// {"kappa", 1}, +// {"kappae", 1}, +// {"sigma", -1}, +// {"kh", -0.00013333333333333}, +// {"ke", -3.55555555555e-8}, +// {"alpha", 1.0}, +// } +// }, +// {"regtestmat1", +// { +// {"mu_r", 1}, +// {"rho", 1}, +// {"cv", 1}, +// {"kappa", 1}, +// {"kappae", 1}, +// {"sigma", -1}, +// } +// }, +// {"regtestmat2", +// { +// {"mu_r", 1}, +// {"rho", 1}, +// {"cv", 1}, +// {"kappa", 1}, +// {"kappae", 1}, +// {"sigma", 1}, +// } +// }, +// {"ideal", +// { +// {"mu_r", 1e8}, +// {"rho", 8120.0}, +// {"cv", 0.420}, +// {"kappa", 20}, +// // {"kh", 0.02}, +// // {"ke", 0.0001}, +// {"ks", 0.0044}, +// {"beta", 1.76835}, +// {"alpha", 1.286} +// } +// }, +// {"hiperco50", +// { +// {{"reluctivity", +// {{"lognu", +// { +// {"cps", +// {5.4010, +// 4.3732, +// 3.9991, +// 3.8783, +// 3.8492, +// 3.8882, +// 4.3043, +// 4.8375, +// 10.2510, +// 13.5846, +// 13.5848} +// }, +// {"knots", +// {0.0, +// 0.0, +// 0.0, +// 0.6209, +// 0.9571, +// 1.1516, +// 1.3460, +// 1.5888, +// 1.8193, +// 2.3539, +// 2.7175, +// 3.5000, +// 3.5000, +// 3.5000} +// }, +// {"degree", 2}, +// } +// }}, +// {{"bh", +// { +// {"cps", +// {0.0, +// 1.52187154e+01, +// 2.44069859e+01, +// 2.87543851e+01, +// 3.28574240e+01, +// 3.53931188e+01, +// 3.83409008e+01, +// 4.10642780e+01, +// 4.38435871e+01, +// 4.64801737e+01, +// 4.96317182e+01, +// 5.29859037e+01, +// 5.66857479e+01, +// 6.15933673e+01, +// 6.64821406e+01, +// 7.75969027e+01, +// 9.75330122e+01, +// 1.21434401e+02, +// 1.52150611e+02, +// 1.94912815e+02, +// 3.26052424e+02, +// 3.16221363e+02, +// 1.17492265e+03, +// 2.37559462e+03, +// 3.70126207e+03, +// 7.18675547e+03, +// 1.35355204e+04, +// 2.17248492e+04, +// 3.97887360e+04} +// }, +// {"knots", +// {0.0, 0.0, 0.0, 0.0, 0.2, 0.3, 0.4, 0.5, 0.6, +// 0.7, 0.8, +// 0.9, 1.0, 1.1, 1.1976, 1.3, 1.4, 1.4775, 1.6, 1.7119, +// 1.8, 1.9, +// 2., 2.1, 2.1868, 2.25, 2.3, 2.36, 2.4, 2.5, 2.5, +// 2.5, 2.5} +// }, +// {"degree", 3} +// } +// }} +// }}, +// {"mu_r", 500}, +// {"rho", 8110.0}, +// {"cv", 0.420}, +// {"kappa", 20}, +// {"ks", 0.0044}, +// {"beta", 1.76835}, +// {"alpha", 1.286} +// } +// }, +// {"hiperco50old", +// { +// {"B", +// // {0.0, +// // 0.0, +// // 0.0, +// // 0.0, +// // 1.424590497285806, +// // 1.879802197738017, +// // 2.089177970766961, +// // 2.184851867536579, +// // 2.229448683, +// // 2.264761102805212, +// // 2.302883806114454, +// // 7.290145827, +// // 7.290145827, +// // 7.290145827, +// // 7.290145827}}, +// {0., 0., 0., 0., 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, +// 0.8, +// 0.9, 1., 1.1, 1.1976, 1.3, 1.4, 1.4775, 1.6, 1.7119, 1.8, 1.9, +// 2., 2.1, 2.1868, 2.25, 2.3, 2.36, 2.4, 2.5, 2.5, 2.5, 2.5} +// }, +// {"H", +// // {0.0, +// // 8.993187962999999, +// // 10.978287105, +// // 83.247282676, +// // 211.230768762, +// // 458.336047457, +// // 1159.131573849, +// // 2773.494054824, +// // 1.342303920594017e6, +// // 2.675328620250853e6, +// // 3.9982409587815357e6}}, +// {-8.45567148e-16, 1.52187154e+01, 2.44069859e+01, 2.87543851e+01, +// 3.28574240e+01, 3.53931188e+01, 3.83409008e+01, 4.10642780e+01, +// 4.38435871e+01, 4.64801737e+01, 4.96317182e+01, 5.29859037e+01, +// 5.66857479e+01, 6.15933673e+01, 6.64821406e+01, 7.75969027e+01, +// 9.75330122e+01, 1.21434401e+02, 1.52150611e+02, 1.94912815e+02, +// 3.26052424e+02, 3.16221363e+02, 1.17492265e+03, 2.37559462e+03, +// 3.70126207e+03, 7.18675547e+03, 1.35355204e+04, 2.17248492e+04, +// 3.97887360e+04} +// }, +// {"mu_r", 500}, +// {"rho", 8110.0}, +// {"cv", 0.420}, +// {"kappa", 20}, +// // {"kh", 0.02}, +// // {"ke", 0.0001}, +// {"ks", 0.0044}, +// {"beta", 1.76835}, +// {"alpha", 1.286} +// } +// }, +// {"ansys-steel", +// { +// {"mu_r", 10000}, +// } +// }, +// {"steel", +// { +// {"B", +// {0.0000000000000000, 0.0044450000000000, 0.0058190000000000, +// 0.0071930000000000, 0.0085680000000000, 0.0128980000000000, +// 0.0172280000000000, 0.0274700000000000, 0.0465790000000000, +// 0.0922889999999999, 0.2207600000000000, 0.4113000000000000, +// 0.6018500000000000, 0.7717000000000000, 0.9149500000000000, +// 1.0464000000000000, 1.1600999999999900, 1.2619000000000000, +// 1.3431000000000000, 1.3947000000000000, 1.4286000000000000, +// 1.4507000000000000, 1.4668000000000000, 1.4830000000000000, +// 1.4932000000000000, 1.5064000000000000, 1.5166999999999900, +// 1.5268999999999900, 1.5371999999999900, 1.5474000000000000, +// 1.5576000000000000, 1.5679000000000000, 1.5840000000000000, +// 1.5972000000000000, 1.6104000000000000, 1.6266000000000000, +// 1.6427000000000000, 1.6589000000000000, 1.6779999999999900, +// 1.6971000000000000, 1.7161999999999900, 1.7353000000000000, +// 1.7604000000000000, 1.7854000000000000, 1.8104000000000000, +// 1.8384000000000000, 1.8723000000000000, 1.9060999999999900, +// 1.9459000000000000, 1.9886999999999900, 2.0344000000000000, +// 2.0741999999999900, 2.1080999999999900, 2.1389999999999900, +// 2.1610999999999900, 2.1772000000000000, 2.1903999999999900} +// }, +// {"H", +// {0.0000000000000000, 9.5103069999999900, +// 11.2124700000000000, 13.2194140000000000, +// 15.5852530000000000, 18.3712620000000000, +// 21.6562209999999000, 25.5213000000000000, +// 30.0619920000000000, 35.3642410000000000, +// 41.4304339999999000, 48.3863029999999000, +// 56.5103700000000000, 66.0660359999999000, +// 77.3405759999999000, 90.5910259999999000, +// 106.2120890000000000, 124.5944920000000000, +// 146.3111910000000000, 172.0624699999990000, +// 202.5247369999990000, 238.5255980000000000, +// 281.0120259999990000, 331.0583149999990000, +// 390.1446090000000000, 459.6953439999990000, +// 541.7317890000000000, 638.4104939999990000, +// 752.3336430000000000, 886.5729270000000000, +// 1044.7729970000000000, 1231.2230800000000000, +// 1450.5386699999900000, 1709.1655450000000000, +// 2013.8677920000000000, 2372.5235849999900000, +// 2795.1596880000000000, 3292.9965269999900000, +// 3878.9256599999900000, 4569.1013169999900000, +// 5382.0650580000000000, 6339.7006929999900000, +// 7465.5631620000000000, 8791.7222000000000000, +// 10352.2369750000000000, 12188.8856750000000000, +// 14347.8232499999000000, 16887.9370500000000000, +// 19872.0933000000000000, 23380.6652749999000000, +// 27504.3713250000000000, 32364.9650250000000000, +// 38095.3407999999000000, 44847.4916749999000000, +// 52819.5656250000000000, 62227.2176750000000000, +// 73321.1169499999000000} +// }, +// {"mu_r", 750}, +// {"rho", 7.750}, +// {"cv", 0.420}, +// {"kappa", 50}, +// // {"kh", 0.02}, +// // {"ke", 0.0001}, +// {"ks", 0.0044}, +// {"beta", 1.76835}, +// {"alpha", 1.286} +// } +// }, +// {"team13", +// { +// {"B", +// {// 0.0, 0.0, 0.0, 0.0, 0.1, +// // 0.4672, 1.1862, 1.4124, 1.6386, 4.3078, 15.8322, 15.8322, +// // 15.8322, 15.8322 0, 0.025, 0.3, +// // 0.7, 1.1, 1.5, 1.7, 1.8 +// 0.0, +// 0.0, +// 0.0, +// 0.0, +// 0.0979, +// 1.3841, +// 1.800000001641453, +// 1.999999999999916, +// 2.22, +// 2.22, +// 2.22, +// 2.22} +// }, +// {"H", +// {// 8.768456660138328, 7.962589775712834, 6.836102567668674, +// // 5.849726164223915, +// // 5.817004866729157, 5.925084803960186, 12.847141274392365, +// // 13.168983405092462, +// // 13.587072660625301, 13.587072660625301 0.0, 93, +// // 222, 272, 377, 933, 4993, 9423 +// 0.0, +// 164.46362274610001, +// 301.3546318106, +// 161.7681804384, +// 961.7045565209, +// 27015.6213073554, +// 138424.0810362731, +// 196780.8935033237} +// }, +// {"mu_r", 750} +// {"B", +// { +// 0, +// 0.0025, +// 0.005, +// 0.0125, +// 0.025, +// 0.05, +// 0.1, +// 0.2, +// 0.3, +// 0.4, +// 0.5, +// 0.6, +// 0.7, +// 0.8, +// 0.9, +// 1.0, +// 1.1, +// 1.2, +// 1.3, +// 1.4, +// 1.5, +// 1.55, +// 1.6, +// 1.65, +// 1.7, +// 1.75, +// 1.8, +// 1.8435526351982585, +// 1.9179901552741234, +// 1.984275166575515, +// 2.042407669102433, +// 2.092387662854877, +// 2.1342151478328484, +// 2.1678901240363464, +// 2.1934125914653704, +// 2.210782550119921, +// 2.219999999999999, +// 2.222831853071796, +// 2.2605309649148735, +// 2.298230076757951, +// 2.3359291886010287, +// 2.373628300444106 +// }}, +// {"H", +// { +// 0.0, +// 16, +// 30, +// 54, +// 93, +// 143, +// 191, +// 210, +// 222, +// 233, +// 247, +// 258, +// 272, +// 289, +// 313, +// 342, +// 377, +// 433, +// 509, +// 648, +// 933, +// 1228, +// 1934, +// 2913, +// 4993, +// 7189, +// 9423, +// 11657.0, +// 15794.62323416157, +// 19932.24646832314, +// 24069.869702484713, +// 28207.492936646286, +// 32345.116170807854, +// 36482.73940496943, +// 40620.362639130995, +// 44757.98587329257, +// 48895.60910745414, +// 50000.0, +// 80000.0, +// 110000.0, +// 140000.0, +// 170000.0 +// }} +// } +// }, +// {"Nd2Fe14B", +// { +// {"mu_r", 1.04}, +// {"B_r", 1.390}, +// // {"B_r", 0.0}, +// {"rho", 7500}, +// {"cv", 502.08}, +// {"kappa", 9}, +// {"max-temp", 310 + 273.15}, // Curie temp is 310 C +// // {"ks", 18.092347463670936}, +// {"ks", 500}, +// {"beta", 0.0}, +// {"alpha", 0.0} +// } +// }, +// {"air", +// { +// {"mu_r", 1}, +// {"rho", 1.225}, +// {"cv", 93}, +// {"kappa", 0.026}, +// {"max-temp", 500} +// } +// }, +// {"copper", // this is meant for the heatsink +// { +// {"mu_r", 1}, +// {"rho", 8960}, +// {"cv", 376}, +// {"kappa", 400} +// // {"sigma", 58.14e6}, +// } +// }, +// {"2024-T3", // motor heatsink from Reference Paper +// { +// {"mu_r", 1}, +// {"rho", 2780}, +// {"cv", 875}, +// {"kappa", 120} +// } +// }, +// {"copperwire", // meant for windings - should have reduced +// // conductivity +// { +// {"mu_r", 1}, +// {"rho", 8960}, +// {"cv", 376}, +// // {"kappa", 237.6}, +// {"kappa", 2.49}, +// {"sigma", 58.14e6}, +// {"max-temp", 400} +// } +// } +// }; } // namespace mach diff --git a/src/physics/electromagnetics/reluctivity_coefficient.cpp b/src/physics/electromagnetics/reluctivity_coefficient.cpp index 4ccf9df6..fe816872 100644 --- a/src/physics/electromagnetics/reluctivity_coefficient.cpp +++ b/src/physics/electromagnetics/reluctivity_coefficient.cpp @@ -1,59 +1,294 @@ +#include #include #include #include #include "mfem.hpp" #include "nlohmann/json.hpp" +#include "tinysplinecxx.h" #include "reluctivity_coefficient.hpp" +#include "utils.hpp" namespace { /// permeability of free space constexpr double mu_0 = 4e-7 * M_PI; +constexpr double nu0 = 1 / mu_0; + +class logNuBBSplineReluctivityCoefficient : public mach::StateCoefficient +{ +public: + /// \brief Define a reluctivity model from a B-Spline fit of + /// log(nu) as a function of B + /// \param[in] cps - spline control points -> nu ~ exp(cps) + /// \param[in] knots - spline knot vector -> B ~ knots + /// \param[in] degree - degree of B-Spline curve + logNuBBSplineReluctivityCoefficient(const std::vector &cps, + const std::vector &knots, + int degree = 3); + + /// \brief Evaluate the reluctivity in the element described by trans at the + /// point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; + + /// \brief Evaluate the derivative of reluctivity with respsect to B in the + /// element described by trans at the point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double EvalStateDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; + + void EvalRevDiff(const double Q_bar, + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + mfem::DenseMatrix &PointMat_bar) override + { } + +protected: + /// max nu value in the data + double lognu_max; + /// max B value in the data + double b_max; + /// spline representing log(nu) + std::unique_ptr lognu; + /// spline representing dlog(nu)/dB + std::unique_ptr dlognudb; +}; + +class BHBSplineReluctivityCoefficient : public mach::StateCoefficient +{ +public: + /// \brief Define a reluctivity model from a B-Spline fit with linear + /// extrapolation at the far end + /// \param[in] B - magnetic flux density values from B-H curve + /// \param[in] H - magnetic field intensity valyes from B-H curve + BHBSplineReluctivityCoefficient(const std::vector &cps, + const std::vector &knots, + int degree = 3); + + /// \brief Evaluate the reluctivity in the element described by trans at the + /// point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; + + /// \brief Evaluate the derivative of reluctivity with respsect to magnetic + /// flux in the element described by trans at the point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double EvalStateDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; + + void EvalRevDiff(const double Q_bar, + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + mfem::DenseMatrix &PointMat_bar) override + { } + + // ~BHBSplineReluctivityCoefficient() override; + +protected: + /// max H value in the data + double h_max; + /// max B value in the data + double b_max; + /// spline representing H(B) + std::unique_ptr bh; + /// spline representing dH(B)/dB + std::unique_ptr dbdh; +}; + +class team13ReluctivityCoefficient : public mach::StateCoefficient +{ +public: + /// \brief Define a reluctivity model for the team13 steel + team13ReluctivityCoefficient() { std::cout << "using team13 coeff!\n"; } + + /// \brief Evaluate the reluctivity in the element described by trans at the + /// point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; + + /// \brief Evaluate the derivative of reluctivity with respsect to magnetic + /// flux in the element described by trans at the point ip. + /// \note When this method is called, the caller must make sure that the + /// IntegrationPoint associated with trans is the same as ip. This can be + /// achieved by calling trans.SetIntPoint(&ip). + double EvalStateDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + double state) override; +}; + +std::unique_ptr constructLinearReluctivityCoeff( + const std::string &material_name, + const nlohmann::json &materials) +{ + std::cout << "materials:\n"; + std::cout << materials << "\n"; + auto mu_r = materials[material_name].value("mu_r", 1.0); + return std::make_unique(1.0 / (mu_r * mu_0)); +} + +void getCpsKnotsAndDegree(const nlohmann::json &material, + const nlohmann::json &materials, + const std::string &model, + std::vector &cps, + std::vector &knots, + int °ree) +{ + const auto &material_name = material["name"].get(); + + if (material["reluctivity"].contains("cps")) + { + cps = material["reluctivity"]["cps"].get>(); + } + else + { + cps = materials[material_name]["reluctivity"][model]["cps"] + .get>(); + } + if (material["reluctivity"].contains("knots")) + { + knots = material["reluctivity"]["knots"].get>(); + } + else + { + knots = materials[material_name]["reluctivity"][model]["knots"] + .get>(); + } + if (material["reluctivity"].contains("degree")) + { + degree = material["reluctivity"]["degree"].get(); + } + else + { + degree = + materials[material_name]["reluctivity"][model].value("degree", 3); + } +} std::unique_ptr constructReluctivityCoeff( const nlohmann::json &component, const nlohmann::json &materials) { std::unique_ptr temp_coeff; - auto material = component["material"].get(); + const auto &material = component["material"]; - auto has_nonlinear = - materials[material].contains("B") && materials[material].contains("H"); - if (component.contains("linear")) + std::cout << "material: " << material << "\n"; + + /// If "material" is a string, it is interpreted to be the name of a + /// material. We default to a linear reluctivity with mu_r = 1.0 unless + /// there is a different value in the material library + if (material.is_string()) { - auto linear = component["linear"].get(); - if (linear) - { - auto mu_r = materials[material]["mu_r"].get(); - temp_coeff = - std::make_unique(1.0 / (mu_r * mu_0)); - } - else - { - auto b = materials[material]["B"].get>(); - auto h = materials[material]["H"].get>(); - temp_coeff = - std::make_unique(b, h); - } + const auto &material_name = material.get(); + temp_coeff = constructLinearReluctivityCoeff(material_name, materials); } else { - if (has_nonlinear) + const auto &material_name = material["name"].get(); + + if (material.contains("reluctivity")) { - auto b = materials[material]["B"].get>(); - auto h = materials[material]["H"].get>(); - temp_coeff = - std::make_unique(b, h); + const auto &nu_model = + material["reluctivity"]["model"].get(); + if (nu_model == "linear") + { + temp_coeff = + constructLinearReluctivityCoeff(material_name, materials); + } + else if (nu_model == "lognu") + { + std::vector cps; + std::vector knots; + int degree = 0; + getCpsKnotsAndDegree( + material, materials, nu_model, cps, knots, degree); + temp_coeff = std::make_unique( + cps, knots, degree); + } + else if (nu_model == "bh") + { + std::vector cps; + std::vector knots; + int degree = 0; + getCpsKnotsAndDegree( + material, materials, nu_model, cps, knots, degree); + temp_coeff = std::make_unique( + cps, knots, degree); + } + else if (nu_model == "team13") + { + throw mach::MachException(""); + } + else + { + std::string error_msg = + "Unrecognized reluctivity model for material \""; + error_msg += material_name; + error_msg += "\"!\n"; + throw mach::MachException(error_msg); + } } else { - auto mu_r = materials[material]["mu_r"].get(); - temp_coeff = - std::make_unique(1.0 / (mu_r * mu_0)); + temp_coeff = constructLinearReluctivityCoeff(material_name, materials); } } + + // auto has_nonlinear = + // materials[material].contains("B") && + // materials[material].contains("H"); + // if (component.contains("linear")) + // { + // auto linear = component["linear"].get(); + // if (linear) + // { + // auto mu_r = materials[material]["mu_r"].get(); + // temp_coeff = + // std::make_unique(1.0 / (mu_r * + // mu_0)); + // } + // else + // { + // auto b = materials[material]["B"].get>(); + // auto h = materials[material]["H"].get>(); + // temp_coeff = std::make_unique(b, + // h); + // } + // } + // else + // { + // if (has_nonlinear) + // { + // auto b = materials[material]["B"].get>(); + // auto h = materials[material]["H"].get>(); + // temp_coeff = std::make_unique(b, h); + // } + // else + // { + // auto mu_r = materials[material]["mu_r"].get(); + // temp_coeff = + // std::make_unique(1.0 / (mu_r * mu_0)); + // } + // } return temp_coeff; } @@ -123,3 +358,306 @@ ReluctivityCoefficient::ReluctivityCoefficient(const nlohmann::json &nu_options, } } // namespace mach + +/// Move these coefficients to their own header file so I can test them +/// Make sure they predict similar nu and dnudb values + +namespace +{ +logNuBBSplineReluctivityCoefficient::logNuBBSplineReluctivityCoefficient( + const std::vector &cps, + const std::vector &knots, + int degree) + : lognu_max(cps[cps.size() - 1]), + b_max(knots[knots.size() - 1]), + lognu(std::make_unique(cps.size(), 1, degree)) +{ + lognu->setControlPoints(cps); + lognu->setKnots(knots); + + dlognudb = std::make_unique(lognu->derive()); +} + +double logNuBBSplineReluctivityCoefficient::Eval( + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + if (state <= b_max) + { + double nu = exp(lognu->eval(state).result()[0]); + return nu; + } + else + { + return nu0; + } +} + +double logNuBBSplineReluctivityCoefficient::EvalStateDeriv( + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + if (state > b_max) + { + std::cout << "lognu state: " << state; + std::cout << " !!!LARGE STATE!!!"; + std::cout << "\n"; + } + + if (state <= b_max) + { + double nu = exp(lognu->eval(state).result()[0]); + double dnudb = nu * dlognudb->eval(state).result()[0]; + return dnudb; + } + else + { + return 0.0; + } +} + +// double logNuBBSplineReluctivityCoefficient::EvalState2ndDeriv( +// mfem::ElementTransformation &trans, +// const mfem::IntegrationPoint &ip, +// const double state) +// { +// if (state <= b_max) +// { +// double t = state / b_max; +// double nu = exp(lognu->eval(t).result()[0]); +// double first_deriv = dlognudb->eval(t).result()[0]; +// double second_deriv = d2lognudb2->eval(t).result()[0]; +// double d2nudb2 = nu * (second_deriv + pow(first_deriv, 2)); +// return d2nudb2; +// } +// else +// { +// return 0.0; +// } +// } + +BHBSplineReluctivityCoefficient::BHBSplineReluctivityCoefficient( + const std::vector &cps, + const std::vector &knots, + int degree) + // : b_max(B[B.size()-1]), nu(H.size(), 1, 3) + : h_max(cps[cps.size() - 1]), + b_max(knots[knots.size() - 1]), + bh(std::make_unique(cps.size(), 1, degree)) +{ + std::vector scaled_knots(knots); + for (int i = 0; i < knots.size(); ++i) + { + scaled_knots[i] = scaled_knots[i] / b_max; + } + bh->setControlPoints(cps); + bh->setKnots(scaled_knots); + + dbdh = std::make_unique(bh->derive()); + // dnudb = nu.derive(); +} + +double BHBSplineReluctivityCoefficient::Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + constexpr double nu0 = 1 / (4e-7 * M_PI); + // std::cout << "eval state state: " << state << "\n"; + if (state <= 1e-14) + { + double t = state / b_max; + double nu = dbdh->eval(t).result()[0] / b_max; + return nu; + } + else if (state <= b_max) + { + double t = state / b_max; + double nu = bh->eval(t).result()[0] / state; + // std::cout << "eval state nu: " << nu << "\n"; + return nu; + } + else + { + return (h_max - nu0 * b_max) / state + nu0; + } +} + +double BHBSplineReluctivityCoefficient::EvalStateDeriv( + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + constexpr double nu0 = 1 / (4e-7 * M_PI); + + /// TODO: handle state == 0 + if (state <= b_max) + { + double t = state / b_max; + double h = bh->eval(t).result()[0]; + return dbdh->eval(t).result()[0] / (state * b_max) - h / pow(state, 2); + } + else + { + return -(h_max - nu0 * b_max) / pow(state, 2); + } +} + +// BHBSplineReluctivityCoefficient::~BHBSplineReluctivityCoefficient() = +// default; + +/** unused +double team13h(double b_hat) +{ + const double h = + exp((0.0011872363994136887 * pow(b_hat, 2) * (15 * pow(b_hat, 2) - 9.0) - + 0.19379133411847338 * pow(b_hat, 2) - + 0.012675319795245974 * b_hat * (3 * pow(b_hat, 2) - 1.0) + + 0.52650810858405916 * b_hat + 0.77170389255937188) / + (-0.037860246476916264 * pow(b_hat, 2) + + 0.085040155318288846 * b_hat + 0.1475250808150366)) - + 31; + return h; +} +*/ + +double team13dhdb_hat(double b_hat) +{ + const double dhdb_hat = + (-0.0013484718812450662 * pow(b_hat, 5) + + 0.0059829967461202211 * pow(b_hat, 4) + + 0.0040413617616232578 * pow(b_hat, 3) - + 0.013804440762666015 * pow(b_hat, 2) - 0.0018970139190370716 * b_hat + + 0.013917259962808418) * + exp((0.017808545991205332 * pow(b_hat, 4) - + 0.038025959385737926 * pow(b_hat, 3) - + 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat + + 0.77170389255937188) / + (-0.037860246476916264 * pow(b_hat, 2) + + 0.085040155318288846 * b_hat + 0.1475250808150366)) / + (0.0014333982632928504 * pow(b_hat, 4) - + 0.0064392824815713142 * pow(b_hat, 3) - + 0.0039388438258098624 * pow(b_hat, 2) + 0.025091111571707653 * b_hat + + 0.02176364946948308); + return dhdb_hat; +} + +double team13d2hdb_hat2(double b_hat) +{ + const double d2hdb_hat2 = + (1.8183764145086082e-6 * pow(b_hat, 10) - + 1.6135805755447689e-5 * pow(b_hat, 9) + + 2.2964027416433258e-5 * pow(b_hat, 8) + + 0.00010295509167249583 * pow(b_hat, 7) - + 0.0001721199302193437 * pow(b_hat, 6) - + 0.00031470749218644612 * pow(b_hat, 5) + + 0.00054873370082066282 * pow(b_hat, 4) + + 0.00078428896855240252 * pow(b_hat, 3) - + 0.00020176627749697931 * pow(b_hat, 2) - + 0.00054403666453702558 * b_hat - 0.00019679534359955033) * + exp((0.017808545991205332 * pow(b_hat, 4) - + 0.038025959385737926 * pow(b_hat, 3) - + 0.20447646171319658 * pow(b_hat, 2) + 0.53918342837930511 * b_hat + + 0.77170389255937188) / + (-0.037860246476916264 * pow(b_hat, 2) + + 0.085040155318288846 * b_hat + 0.1475250808150366)) / + (2.0546305812109595e-6 * pow(b_hat, 8) - + 1.8460112651872795e-5 * pow(b_hat, 7) + + 3.0172495078875982e-5 * pow(b_hat, 6) + + 0.00012265776759231136 * pow(b_hat, 5) - + 0.0002452310649846335 * pow(b_hat, 4) - + 0.00047794451332165656 * pow(b_hat, 3) + + 0.00045811664722395466 * pow(b_hat, 2) + 0.001092148314092672 * b_hat + + 0.00047365643823053111); + return d2hdb_hat2; +} + +double team13b_hat(double b) +{ + const double b_hat = 1.10803324099723 * b + 1.10803324099723 * atan(20 * b) - + 0.9944598337950139; + return b_hat; +} + +double team13db_hatdb(double b) +{ + const double db_hatdb = (443.213296398892 * pow(b, 2) + 23.26869806094183) / + (400 * pow(b, 2) + 1); + return db_hatdb; +} + +double team13d2b_hatdb2(double b) +{ + const double d2b_hatdb2 = + -17728.53185595568 * b / pow(400 * pow(b, 2) + 1, 2); + return d2b_hatdb2; +} + +double team13ReluctivityCoefficient::Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + if (state > 2.2) + { + return 1 / (4 * M_PI * 1e-7); + } + const double b_hat = team13b_hat(state); + const double db_hatdb = team13db_hatdb(state); + + const double dhdb_hat = team13dhdb_hat(b_hat); + + const double nu = dhdb_hat * db_hatdb; + // std::cout << "state: " << state << " nu: " << nu << "\n"; + + // try + // { + // if (!isfinite(nu)) + // { + // throw MachException("nan!"); + // } + // } + // catch(const std::exception& e) + // { + // std::cerr << e.what() << '\n'; + // } + + return nu; +} + +double team13ReluctivityCoefficient::EvalStateDeriv( + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + if (state > 2.2) + { + return 0; + } + const double b_hat = team13b_hat(state); + const double db_hatdb = team13db_hatdb(state); + const double d2b_hatdb2 = team13d2b_hatdb2(state); + + const double dhdb_hat = team13dhdb_hat(b_hat); + const double d2hdb_hat2 = team13d2hdb_hat2(b_hat); + + // const double dnudb = d2hdb_hat2 * pow(db_hatdb, 2) + dhdb_hat * + // d2b_hatdb2; std::cout << "state: " << state << " dnudb: " << dnudb << + // "\n"; + + // try + // { + // if (!isfinite(dnudb)) + // { + // throw MachException("nan!"); + // } + // } + // catch(const std::exception& e) + // { + // std::cerr << e.what() << '\n'; + // } + + return d2hdb_hat2 * pow(db_hatdb, 2) + dhdb_hat * d2b_hatdb2; +} + +} // namespace diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index b607686f..6025f147 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -345,6 +345,25 @@ PDESolver::PDESolver(MPI_Comm incomm, mesh_(constructMesh(comm, options["mesh"], std::move(smesh))), materials(material_library) { + /// loop over all components specified in options and add their specified + /// materials to the solver's known material library + if (solver_options.contains("components")) + { + for (auto &component : solver_options["components"]) + { + const auto &material = component["material"]; + if (material.is_string()) + { + continue; + } + else + { + const auto &material_name = material["name"].get(); + materials[material_name] = material; + } + } + } + fields.emplace( "state", FiniteElementState(mesh(), options["space-dis"], num_states, "state")); diff --git a/test/regression/test_magnetostatic_box.cpp b/test/regression/test_magnetostatic_box.cpp index 3e91d958..a1eb225e 100644 --- a/test/regression/test_magnetostatic_box.cpp +++ b/test/regression/test_magnetostatic_box.cpp @@ -49,15 +49,19 @@ auto options = R"( "abstol": 1e-9 }, "components": { - "attr1": { - "material": "box1", - "attr": 1, - "linear": true + "box1": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + } }, - "attr2": { - "material": "box2", - "attr": 2, - "linear": true + "box2": { + "attrs": [2], + "material": { + "name": "box2", + "mu_r": 795774.7154594767 + } } }, "current": { diff --git a/test/regression/test_magnetostatic_box2d.cpp b/test/regression/test_magnetostatic_box2d.cpp index b28acedc..70aa908a 100644 --- a/test/regression/test_magnetostatic_box2d.cpp +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -49,15 +49,19 @@ auto options = R"( "abstol": 1e-9 }, "components": { - "attr1": { - "material": "box1", - "attr": 1, - "linear": true + "box1": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + } }, - "attr2": { - "material": "box2", - "attr": 2, - "linear": true + "box2": { + "attrs": [2], + "material": { + "name": "box2", + "mu_r": 795774.7154594767 + } } }, "current": { diff --git a/test/regression/test_thermal_cube.cpp b/test/regression/test_thermal_cube.cpp index 04c34ceb..b4f36355 100644 --- a/test/regression/test_thermal_cube.cpp +++ b/test/regression/test_thermal_cube.cpp @@ -36,8 +36,11 @@ TEST_CASE("ThermalSolver Box Regression Test") }, "components": { "box": { - "material": "box1", - "attr": 1 + "attrs": [1], + "material": { + "name": "box1", + "kappa": 1 + } } }, "bcs": { @@ -132,8 +135,11 @@ TEST_CASE("ThermalSolver Box Regression Test with load") }, "components": { "box": { - "material": "box1", - "attr": 1 + "attrs": [1], + "material": { + "name": "box1", + "kappa": 1 + } } }, "bcs": { diff --git a/test/unit/test_coefficient.cpp b/test/unit/test_coefficient.cpp index 4b0f0ec0..fd9b1c50 100644 --- a/test/unit/test_coefficient.cpp +++ b/test/unit/test_coefficient.cpp @@ -1,4 +1,5 @@ #include +#include #include "catch.hpp" #include "mfem.hpp" @@ -8,6 +9,7 @@ #include "material_library.hpp" #include "electromag_test_data.hpp" +#include "reluctivity_coefficient.hpp" namespace { @@ -457,19 +459,26 @@ TEST_CASE("SteinmetzVectorDiffCoefficient::Eval", } */ -TEST_CASE("NonlinearReluctivityCoefficient::EvalStateDeriv", - "[NonlinearReluctivityCoefficient]") +void printVector(const std::vector &vector) +{ + for (int k = 0; k < vector.size(); ++k) + { + std::cout << vector[k] << ", "; + } +} + +TEST_CASE("ReluctivityCoefficient lognu vs bh") { using namespace mfem; using namespace mach; - constexpr double eps_fd = 1e-5; - constexpr int dim = 3; - std::stringstream meshStr; meshStr << two_tet_mesh_str; Mesh mesh(meshStr); + const int dim = mesh.SpaceDimension(); + + /// Costruct coefficient for (int p = 1; p <= 1; p++) { @@ -477,23 +486,59 @@ TEST_CASE("NonlinearReluctivityCoefficient::EvalStateDeriv", ND_FECollection fec(p, dim); FiniteElementSpace fes(&mesh, &fec); - GridFunction A(&fes); - VectorFunctionCoefficient pert(dim, [](const Vector &x, Vector &A) + // "cps": [5.4094, 4.5222, 3.8259, 3.7284, 5.1554, 11.1488, 13.0221, 13.5798, 13.5808, 13.5814], + + const auto &lognu_options = R"( { - A(0) = -0.5*x(1); - A(1) = 1.79*x(0); - A(2) = 0.0; - }); - A.ProjectCoefficient(pert); + "components": { + "test": { + "attrs": 1, + "material": { + "name": "hiperco50", + "reluctivity": { + "model": "lognu", + "cps": [5.40954787023781, 4.50729991768494, 3.82622972028510, 3.73337170624446, 5.16008222491274, 11.0710865890706, 12.6733270435251, 13.5870714039890, 13.5870714039890, 13.5870714039890], + "knots": [0, 0, 0, 0, 0.754565031471487, 1.71725877985567, 2.14583020842710, 2.57440163699853, 3.00297306556996, 3.56974470521025, 6, 6, 6, 6], + "degree": 3 + } + } + } + } + })"_json; + auto lognu_coeff = ReluctivityCoefficient(lognu_options, material_library); + const auto &bh_options = R"( + { + "components": { + "test": { + "attrs": 1, + "material": { + "name": "hiperco50", + "reluctivity": { + "model": "bh" + } + } + } + } + })"_json; + auto bh_coeff = ReluctivityCoefficient(bh_options, material_library); - auto b = material_library["hiperco50"]["B"].get>(); - auto h = material_library["hiperco50"]["H"].get>(); - // auto b = material_library["team13"]["B"].get>(); - // auto h = material_library["team13"]["H"].get>(); - mach::NonlinearReluctivityCoefficient coeff(b, h); + int npts = 1000; + std::vector b_mags(npts); + double b_max = 10.0; + for (int i = 0; i < npts; ++i) + { + b_mags[i] = double(i) / double(npts) * b_max; + } - for (int j = 0; j < fes.GetNE(); j++) + std::vector lognu_nu(npts); + std::vector lognu_dnudb(npts); + + std::vector bh_nu(npts); + std::vector bh_dnudb(npts); + + // for (int j = 0; j < fes.GetNE(); j++) + for (int j = 0; j < 1; j++) { const FiniteElement &el = *fes.GetFE(j); @@ -507,26 +552,115 @@ TEST_CASE("NonlinearReluctivityCoefficient::EvalStateDeriv", ir = &IntRules.Get(el.GetGeomType(), order); } - for (int i = 0; i < ir->GetNPoints(); i++) + // for (int i = 0; i < ir->GetNPoints(); i++) + for (int i = 0; i < 1; i++) { const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - Vector b_vec; - A.GetCurl(trans, b_vec); - - auto b_mag = b_vec.Norml2(); - double dnudB = coeff.EvalStateDeriv(trans, ip, b_mag); + for (int k = 0; k < npts; ++k) + { + auto b_mag = b_mags[k]; - double dnudB_fd = -coeff.Eval(trans, ip, b_mag - eps_fd); - dnudB_fd += coeff.Eval(trans, ip, b_mag + eps_fd); - dnudB_fd /= (2* eps_fd); + lognu_nu[k] = lognu_coeff.Eval(trans, ip, b_mag); + lognu_dnudb[k] = lognu_coeff.EvalStateDeriv(trans, ip, b_mag); + bh_nu[k] = bh_coeff.Eval(trans, ip, b_mag); + bh_dnudb[k] = bh_coeff.EvalStateDeriv(trans, ip, b_mag); + } - // std::cout << "dnudB: " << dnudB << "\n"; - // std::cout << "dnudB_fd: " << dnudB_fd << "\n"; - REQUIRE(dnudB == Approx(dnudB_fd)); + std::cout << "b = np.array(["; + printVector(b_mags); + std::cout << "])\n"; + + std::cout << "lognu_nu = np.array(["; + printVector(lognu_nu); + std::cout << "])\n"; + std::cout << "lognu_dnudb = np.array(["; + printVector(lognu_dnudb); + std::cout << "])\n"; + + std::cout << "bh_nu = np.array(["; + printVector(bh_nu); + std::cout << "])\n"; + std::cout << "bh_dnudb = np.array([np."; + printVector(bh_dnudb); + std::cout << "])\n"; } } } } + +// TEST_CASE("NonlinearReluctivityCoefficient::EvalStateDeriv", +// "[NonlinearReluctivityCoefficient]") +// { +// using namespace mfem; +// using namespace mach; + +// constexpr double eps_fd = 1e-5; +// constexpr int dim = 3; + +// std::stringstream meshStr; +// meshStr << two_tet_mesh_str; +// Mesh mesh(meshStr); + +// /// Costruct coefficient +// for (int p = 1; p <= 1; p++) +// { +// /// construct elements +// ND_FECollection fec(p, dim); +// FiniteElementSpace fes(&mesh, &fec); + +// GridFunction A(&fes); +// VectorFunctionCoefficient pert(dim, [](const Vector &x, Vector &A) +// { +// A(0) = -0.5*x(1); +// A(1) = 1.79*x(0); +// A(2) = 0.0; +// }); +// A.ProjectCoefficient(pert); + + +// auto b = material_library["hiperco50"]["B"].get>(); +// auto h = material_library["hiperco50"]["H"].get>(); +// // auto b = material_library["team13"]["B"].get>(); +// // auto h = material_library["team13"]["H"].get>(); +// mach::NonlinearReluctivityCoefficient coeff(b, h); + +// for (int j = 0; j < fes.GetNE(); j++) +// { + +// const FiniteElement &el = *fes.GetFE(j); + +// IsoparametricTransformation trans; +// mesh.GetElementTransformation(j, &trans); + +// const IntegrationRule *ir = NULL; +// { +// int order = trans.OrderW() + 2 * el.GetOrder(); +// ir = &IntRules.Get(el.GetGeomType(), order); +// } + +// for (int i = 0; i < ir->GetNPoints(); i++) +// { +// const IntegrationPoint &ip = ir->IntPoint(i); + +// trans.SetIntPoint(&ip); +// Vector b_vec; +// A.GetCurl(trans, b_vec); + +// auto b_mag = b_vec.Norml2(); + +// double dnudB = coeff.EvalStateDeriv(trans, ip, b_mag); + +// double dnudB_fd = -coeff.Eval(trans, ip, b_mag - eps_fd); +// dnudB_fd += coeff.Eval(trans, ip, b_mag + eps_fd); +// dnudB_fd /= (2* eps_fd); + +// // std::cout << "dnudB: " << dnudB << "\n"; +// // std::cout << "dnudB_fd: " << dnudB_fd << "\n"; +// REQUIRE(dnudB == Approx(dnudB_fd)); +// } +// } +// } +// } diff --git a/test/unit/test_magnetic_load.cpp b/test/unit/test_magnetic_load.cpp index 537d58c6..369183a8 100644 --- a/test/unit/test_magnetic_load.cpp +++ b/test/unit/test_magnetic_load.cpp @@ -63,7 +63,14 @@ TEST_CASE("MagneticLoad Value Test") })"_json; mfem::ConstantCoefficient nu(1.0); ///(M_PI*4e-7)); - MagneticLoad load_0(diff_stack, fes, fields, options, material_library, nu); + auto test_mat = R"( + { + "testmat": { + "mu_r": 1.0, + "B_r": 1.0 + } + })"_json; + MagneticLoad load_0(diff_stack, fes, fields, options, test_mat, nu); MagneticLoad load(std::move(load_0)); @@ -126,7 +133,14 @@ TEST_CASE("MagneticLoad vectorJacobianProduct wrt mesh_coords") })"_json; mfem::ConstantCoefficient nu(1.0); ///(M_PI*4e-7)); - MagneticLoad load(diff_stack, fes, fields, options, material_library, nu); + auto test_mat = R"( + { + "testmat": { + "mu_r": 1.0, + "B_r": 1.0 + } + })"_json; + MagneticLoad load(diff_stack, fes, fields, options, test_mat, nu); mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); mesh_coords.setTrueVec(mesh_coords_tv); From b32d3cbeb02aef2f86af36f2857ad36cee7bdb92 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 22 Aug 2022 14:48:28 -0600 Subject: [PATCH 32/72] add Newton interations to linesearch test to see where it fails on CI --- src/physics/electromagnetics/reluctivity_coefficient.cpp | 4 ---- src/physics/pde_solver.cpp | 2 +- test/unit/test_linesearch.cpp | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/physics/electromagnetics/reluctivity_coefficient.cpp b/src/physics/electromagnetics/reluctivity_coefficient.cpp index fe816872..bbce9b53 100644 --- a/src/physics/electromagnetics/reluctivity_coefficient.cpp +++ b/src/physics/electromagnetics/reluctivity_coefficient.cpp @@ -140,8 +140,6 @@ std::unique_ptr constructLinearReluctivityCoeff( const std::string &material_name, const nlohmann::json &materials) { - std::cout << "materials:\n"; - std::cout << materials << "\n"; auto mu_r = materials[material_name].value("mu_r", 1.0); return std::make_unique(1.0 / (mu_r * mu_0)); } @@ -191,8 +189,6 @@ std::unique_ptr constructReluctivityCoeff( std::unique_ptr temp_coeff; const auto &material = component["material"]; - std::cout << "material: " << material << "\n"; - /// If "material" is a string, it is interpreted to be the name of a /// material. We default to a linear reluctivity with mu_r = 1.0 unless /// there is a different value in the material library diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index 6025f147..e02cb723 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -359,7 +359,7 @@ PDESolver::PDESolver(MPI_Comm incomm, else { const auto &material_name = material["name"].get(); - materials[material_name] = material; + materials[material_name].merge_patch(material); } } } diff --git a/test/unit/test_linesearch.cpp b/test/unit/test_linesearch.cpp index 16f546f5..cececcbc 100644 --- a/test/unit/test_linesearch.cpp +++ b/test/unit/test_linesearch.cpp @@ -155,7 +155,7 @@ TEST_CASE("RelaxedNewton with BacktrackingLineSearch") mach::RelaxedNewton newton(MPI_COMM_SELF, newton_opts); // mfem::NewtonSolver newton(MPI_COMM_SELF); - // newton.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); + newton.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); newton.SetMaxIter(500); newton.SetAbsTol(1e-6); newton.SetRelTol(1e-6); From 066b0d48a4ef37e778d3c2a25a9f743a6a8fd663 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 23 Aug 2022 12:33:22 -0400 Subject: [PATCH 33/72] bump pybind version to 2.10.0 --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7ec7a72..4c5da3de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,7 +288,8 @@ if (BUILD_PYTHON_WRAPPER) endif (POLICY CMP0127) FetchContent_Declare(pybind11 GIT_REPOSITORY "https://github.com/pybind/pybind11" - GIT_TAG v2.7.1 + #GIT_TAG v2.7.1 + GIT_TAG v2.10.0 ) FetchContent_MakeAvailable(pybind11) From f84864ed558a630cb23683230751344195ea0f98 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 23 Aug 2022 19:17:45 -0400 Subject: [PATCH 34/72] fix linesearch test on linux. For some reason switching from GMRES to CG solved the issue we were seeing --- src/common/linesearch.cpp | 21 ++++++++++++--------- src/common/linesearch.hpp | 10 +++++----- src/common/relaxed_newton.cpp | 23 +++++++++-------------- test/unit/test_linesearch.cpp | 6 +++--- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp index 6fcd0f1c..b4b24703 100644 --- a/src/common/linesearch.cpp +++ b/src/common/linesearch.cpp @@ -103,22 +103,25 @@ double BacktrackingLineSearch::search(const std::function &phi, return alpha2; } -Phi::Phi(const std::function - &calcRes, +Phi::Phi(const std::function &calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, mfem::Operator &jac) - : phi0(residual.Norml2()), - calcRes(calcRes), + : calcRes(calcRes), state(state), descent_dir(descent_dir), scratch(state.Size()), - residual(residual) -{ - jac.Mult(descent_dir, scratch); - dphi0 = -(scratch * residual) / phi0; -} + residual(residual), + phi0(residual.Norml2()), + dphi0( + [&]() + { + jac.Mult(descent_dir, scratch); + return -(scratch * residual) / phi0; + // return -phi0; + }()) +{ } double Phi::operator()(double alpha) { diff --git a/src/common/linesearch.hpp b/src/common/linesearch.hpp index d78afbbd..e87109b1 100644 --- a/src/common/linesearch.hpp +++ b/src/common/linesearch.hpp @@ -38,8 +38,7 @@ class BacktrackingLineSearch : public LineSearch class Phi { public: - Phi(const std::function - &calcRes, + Phi(const std::function &calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, @@ -47,15 +46,16 @@ class Phi double operator()(double alpha); - double phi0; - double dphi0; - private: const std::function &calcRes; const mfem::Vector &state; const mfem::Vector &descent_dir; mfem::Vector scratch; mfem::Vector &residual; + +public: + const double phi0; + const double dphi0; }; } // namespace mach diff --git a/src/common/relaxed_newton.cpp b/src/common/relaxed_newton.cpp index ff3c44bc..8b0c5e66 100644 --- a/src/common/relaxed_newton.cpp +++ b/src/common/relaxed_newton.cpp @@ -11,18 +11,6 @@ namespace { -void calcResidual(const mfem::Operator &oper, - const mfem::Vector &x, - const mfem::Vector &b, - mfem::Vector &res) -{ - const bool have_b = (b.Size() == oper.Height()); - oper.Mult(x, res); - if (have_b) - { - res -= b; - } -} std::unique_ptr createLineSearch( const std::string &type, @@ -80,9 +68,16 @@ double RelaxedNewton::ComputeScalingFactor(const mfem::Vector &x, const mfem::Vector &b) const { auto calcRes = [&](const mfem::Vector &x, mfem::Vector &res) - { calcResidual(*oper, x, b, res); }; + { + oper->Mult(x, res); + const bool have_b = (b.Size() == Height()); + if (have_b) + { + res -= b; + } + }; - auto phi = Phi(calcRes, x, c, r, *grad); + Phi phi(calcRes, x, c, r, *grad); return ls->search(phi, phi.phi0, phi.dphi0, 1.0); } diff --git a/test/unit/test_linesearch.cpp b/test/unit/test_linesearch.cpp index cececcbc..a4f26ee5 100644 --- a/test/unit/test_linesearch.cpp +++ b/test/unit/test_linesearch.cpp @@ -161,9 +161,9 @@ TEST_CASE("RelaxedNewton with BacktrackingLineSearch") newton.SetRelTol(1e-6); newton.SetOperator(oper); - mfem::GMRESSolver gmres(MPI_COMM_SELF); - // gmres.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); - newton.SetSolver(gmres); + mfem::CGSolver cg(MPI_COMM_SELF); + // cg.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); + newton.SetSolver(cg); mfem::Vector zero; mfem::Vector state(2); From 0de877e3048a32d8931fcf5f5aeaba2749302088 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 23 Aug 2022 19:18:08 -0400 Subject: [PATCH 35/72] make some fixes based on clang-tidy suggestions --- mach/pyMach/machSolver.cpp | 14 +++--- src/common/coefficient.cpp | 4 +- src/common/functional_output.cpp | 2 +- src/common/inexact_newton.cpp | 6 +-- src/common/mach_linearform.cpp | 2 +- src/common/matrix_operators.cpp | 24 +++++----- src/common/matrix_operators.hpp | 2 +- src/common/mfem_extensions.cpp | 46 +++++++++---------- src/common/mfem_extensions.hpp | 14 +++--- src/common/ode.cpp | 2 +- .../current_source_functions.cpp | 28 +++++------ .../electromagnetics/electromag_integ.cpp | 4 +- .../electromagnetics/electromag_integ.hpp | 4 +- .../magnetic_source_functions.cpp | 14 +++--- .../reluctivity_coefficient.cpp | 4 +- src/physics/fluidflow/flow_control_solver.cpp | 3 +- src/physics/fluidflow/flow_solver.cpp | 3 +- src/physics/mach_nonlinearform.cpp | 2 +- src/physics/meshmove/mesh_move_integ.hpp | 2 +- src/physics/mfem_common_integ.cpp | 2 +- src/physics/pde_solver.cpp | 11 +++-- src/physics/pde_solver.hpp | 2 +- src/physics/thermal/thermal_residual.cpp | 2 +- src/utils/l2_transfer_operator.cpp | 24 +++++----- src/utils/mesh_warper/mesh_warper.cpp | 8 ++-- src/utils/utils.cpp | 7 ++- 26 files changed, 118 insertions(+), 118 deletions(-) diff --git a/mach/pyMach/machSolver.cpp b/mach/pyMach/machSolver.cpp index e7d0b911..e7b731ef 100644 --- a/mach/pyMach/machSolver.cpp +++ b/mach/pyMach/machSolver.cpp @@ -46,7 +46,7 @@ void initSolver(py::module &m) .def( "setState", [](AbstractSolver2 &self, - std::function fun, + const std::function &fun, const py::array_t &state, const std::string &name) { @@ -59,7 +59,7 @@ void initSolver(py::module &m) .def( "setState", [](AbstractSolver2 &self, - std::function fun, + const std::function &fun, const py::array_t &state, const std::string &name) { @@ -90,7 +90,7 @@ void initSolver(py::module &m) .def( "calcStateError", [](AbstractSolver2 &self, - std::function ex_sol, + const std::function &ex_sol, const py::array_t &state, const std::string &name) { return self.calcStateError( @@ -102,7 +102,7 @@ void initSolver(py::module &m) .def( "calcStateError", [](AbstractSolver2 &self, - std::function ex_sol, + const std::function &ex_sol, const py::array_t &state, const std::string &name) { self.calcStateError(ex_sol, npBufferToMFEMVector(state), name); }, @@ -112,8 +112,8 @@ void initSolver(py::module &m) .def( "calcStateError", [](AbstractSolver2 &self, - std::function - ex_sol, + const std::function &ex_sol, const py::array_t &state, const std::string &name) { @@ -243,7 +243,7 @@ void initSolver(py::module &m) const std::string &output, const py::dict &py_inputs) { return self.calcOutput(output, pyDictToMachInputs(py_inputs)); }, - "Calculate the output specified by \"output\" using \"inputs\"", + R"(Calculate the output specified by "output" using "inputs")", py::arg("output"), py::arg("inputs")) .def( diff --git a/src/common/coefficient.cpp b/src/common/coefficient.cpp index 37b41c7d..a3af9460 100644 --- a/src/common/coefficient.cpp +++ b/src/common/coefficient.cpp @@ -202,7 +202,7 @@ std::unique_ptr constructMaterialCoefficient( { auto material_coeff = std::make_unique(); /// loop over all components, construct coeff for each - for (auto &component : components) + for (const auto &component : components) { int attr = component.value("attr", -1); @@ -226,7 +226,7 @@ std::unique_ptr constructMaterialCoefficient( } else { - for (auto &attribute : component["attrs"]) + for (const auto &attribute : component["attrs"]) { auto coeff = std::make_unique(val); material_coeff->addCoefficient(attribute, move(coeff)); diff --git a/src/common/functional_output.cpp b/src/common/functional_output.cpp index 44fd957c..b519e783 100644 --- a/src/common/functional_output.cpp +++ b/src/common/functional_output.cpp @@ -16,7 +16,7 @@ void setInputs(FunctionalOutput &output, const MachInputs &inputs) { if (std::holds_alternative(input)) { - if (output.func_fields) + if (output.func_fields != nullptr) { auto it = output.func_fields->find(name); if (it != output.func_fields->end()) diff --git a/src/common/inexact_newton.cpp b/src/common/inexact_newton.cpp index dfea2ff6..2553e7af 100644 --- a/src/common/inexact_newton.cpp +++ b/src/common/inexact_newton.cpp @@ -130,14 +130,14 @@ void InexactNewton::Mult(const Vector &b, Vector &x) const if (norm <= norm_goal) { - converged = 1; + converged = true; final_iter = it; break; } if (it >= max_iter) { - converged = 0; + converged = false; final_iter = it; break; } @@ -152,7 +152,7 @@ void InexactNewton::Mult(const Vector &b, Vector &x) const if (c_scale == 0.0) { - converged = 0; + converged = true; final_iter = it; break; } diff --git a/src/common/mach_linearform.cpp b/src/common/mach_linearform.cpp index 6539bf57..18c29b4a 100644 --- a/src/common/mach_linearform.cpp +++ b/src/common/mach_linearform.cpp @@ -36,7 +36,7 @@ void setInputs(MachLinearForm &load, const MachInputs &inputs) { if (std::holds_alternative(input)) { - if (load.lf_fields) + if (load.lf_fields != nullptr) { auto it = load.lf_fields->find(name); if (it != load.lf_fields->end()) diff --git a/src/common/matrix_operators.cpp b/src/common/matrix_operators.cpp index e2faf704..b8f258bd 100644 --- a/src/common/matrix_operators.cpp +++ b/src/common/matrix_operators.cpp @@ -103,7 +103,7 @@ void JacobianFree::Mult(const mfem::Vector &x, mfem::Vector &y) const y *= scale; } } - if (explicit_part) + if (explicit_part != nullptr) { // Include contribution from explicit operator, if necessary explicit_part->Mult(x, state_pert); @@ -117,7 +117,7 @@ mfem::Operator &JacobianFree::getDiagonalBlock(int i) const // We assume that `state` holds where the Jacobian is to be evaluated auto inputs = MachInputs({{"state", state}}); Operator &jac = getJacobianBlock(res, inputs, i); - BlockOperator *block_op = dynamic_cast(explicit_part); + auto *block_op = dynamic_cast(explicit_part); if (explicit_part != nullptr && block_op == nullptr) { throw MachException( @@ -127,27 +127,27 @@ mfem::Operator &JacobianFree::getDiagonalBlock(int i) const } // Case 1: HypreParMatrix - HypreParMatrix *hypre_jac = dynamic_cast(&jac); - if (hypre_jac) + auto *hypre_jac = dynamic_cast(&jac); + if (hypre_jac != nullptr) { *hypre_jac *= scale; - if (block_op) + if (block_op != nullptr) { Operator &exp_block = block_op->GetBlock(i, i); - HypreParMatrix *hypre_exp = dynamic_cast(&exp_block); + auto *hypre_exp = dynamic_cast(&exp_block); *hypre_jac += *hypre_exp; } return jac; } // Case 2: DenseMatrix - DenseMatrix *dense_jac = dynamic_cast(&jac); - if (dense_jac) + auto *dense_jac = dynamic_cast(&jac); + if (dense_jac != nullptr) { *dense_jac *= scale; - if (block_op) + if (block_op != nullptr) { Operator &exp_block = block_op->GetBlock(i, i); - DenseMatrix *dense_exp = dynamic_cast(&exp_block); + auto *dense_exp = dynamic_cast(&exp_block); *dense_jac += *dense_exp; } return jac; @@ -176,7 +176,7 @@ double JacobianFree::getStepSize(const mfem::Vector &baseline, } } -void JacobianFree::print(string file_name) const +void JacobianFree::print(const std::string &file_name) const { remove(file_name.c_str()); ofstream matrix_file(file_name, fstream::app); @@ -199,7 +199,7 @@ void JacobianFree::print(string file_name) const { y *= scale; } - if (explicit_part) + if (explicit_part != nullptr) { state_pert = 0.0; state_pert(j) = 1.0; diff --git a/src/common/matrix_operators.hpp b/src/common/matrix_operators.hpp index 00916a3c..bae4d963 100644 --- a/src/common/matrix_operators.hpp +++ b/src/common/matrix_operators.hpp @@ -243,7 +243,7 @@ class JacobianFree : public mfem::Operator /// Write a file with the explicit matrix entries /// \param[in] file_name - file name to open and write to - void print(std::string file_name) const; + void print(const std::string &file_name) const; private: static constexpr double zero = 1e-16; diff --git a/src/common/mfem_extensions.cpp b/src/common/mfem_extensions.cpp index 1ca28718..5156f7f6 100644 --- a/src/common/mfem_extensions.cpp +++ b/src/common/mfem_extensions.cpp @@ -100,19 +100,19 @@ void RRKImplicitMidpointSolver::Step(Vector &x, double &t, double &dt) t += gamma * dt; } -ExplicitRRKSolver::ExplicitRRKSolver(int s_, - const double *a_, - const double *b_, - const double *c_, - std::ostream *out_stream) - : out(out_stream) -{ - s = s_; - a = a_; - b = b_; - c = c_; - k = new Vector[s]; -} +// ExplicitRRKSolver::ExplicitRRKSolver(int s_, +// const double *a_, +// const double *b_, +// const double *c_, +// std::ostream *out_stream) +// : out(out_stream) +// { +// s = s_; +// a = a_; +// b = b_; +// c = c_; +// k = new Vector[s]; +// } void ExplicitRRKSolver::Init(TimeDependentOperator &f_) { @@ -201,7 +201,7 @@ void ExplicitRRKSolver::Step(Vector &x, double &t, double &dt) t += gamma * dt; } -ExplicitRRKSolver::~ExplicitRRKSolver() { delete[] k; } +// ExplicitRRKSolver::~ExplicitRRKSolver() { delete[] k; } const double RRK6Solver::a[] = {.6e-1, .1923996296296296296296296296296296296296e-1, @@ -251,12 +251,12 @@ const double RRK6Solver::c[] = { BlockJacobiPreconditioner::BlockJacobiPreconditioner(const Array &offsets_) : Solver(offsets_.Last()), - owns_blocks(0), + owns_blocks(false), nBlocks(offsets_.Size() - 1), offsets(0), op(nBlocks) { - op = static_cast(NULL); + op = static_cast(nullptr); offsets.MakeRef(offsets_); } @@ -267,7 +267,7 @@ void BlockJacobiPreconditioner::SetDiagonalBlock(int iblock, Solver *opt) // MFEM_VERIFY(offsets[iblock+1] - offsets[iblock] == opt->Height() && // offsets[iblock+1] - offsets[iblock] == opt->Width(), // "incompatible Operator dimensions"); - if (owns_blocks && op[iblock]) + if (owns_blocks && (op[iblock] != nullptr)) { delete op[iblock]; } @@ -276,20 +276,20 @@ void BlockJacobiPreconditioner::SetDiagonalBlock(int iblock, Solver *opt) void BlockJacobiPreconditioner::SetOperator(const Operator &input_op) { - auto block_op = dynamic_cast(&input_op); + const auto *block_op = dynamic_cast(&input_op); if (block_op != nullptr) { // input_op is a BlockOperator for (int i = 0; i < nBlocks; ++i) { - if (op[i]) + if (op[i] != nullptr) { op[i]->SetOperator(block_op->GetBlock(i, i)); } } return; } - auto jacfree_op = dynamic_cast(&input_op); + const auto *jacfree_op = dynamic_cast(&input_op); if (jacfree_op != nullptr) { // jacfree_op->print("jac-free-matrix.dat"); @@ -298,7 +298,7 @@ void BlockJacobiPreconditioner::SetOperator(const Operator &input_op) // input op is a JacobianFree operator for (int i = 0; i < nBlocks; ++i) { - if (op[i]) + if (op[i] != nullptr) { op[i]->SetOperator(jacfree_op->getDiagonalBlock(i)); } @@ -326,7 +326,7 @@ void BlockJacobiPreconditioner::Mult(const Vector &x, Vector &y) const for (int i = 0; i < nBlocks; ++i) { - if (op[i]) + if (op[i] != nullptr) { op[i]->Mult(xblock.GetBlock(i), yblock.GetBlock(i)); } @@ -357,7 +357,7 @@ void BlockJacobiPreconditioner::MultTranspose(const Vector &x, Vector &y) const for (int i = 0; i < nBlocks; ++i) { - if (op[i]) + if (op[i] != nullptr) { (op[i])->MultTranspose(xblock.GetBlock(i), yblock.GetBlock(i)); } diff --git a/src/common/mfem_extensions.hpp b/src/common/mfem_extensions.hpp index 5d2ca904..7b931cca 100644 --- a/src/common/mfem_extensions.hpp +++ b/src/common/mfem_extensions.hpp @@ -61,20 +61,22 @@ class ExplicitRRKSolver : public mfem::ODESolver const double *a_, const double *b_, const double *c_, - std::ostream *out_stream = nullptr); + std::ostream *out_stream = nullptr) + : s(s_), a(a_), b(b_), c(c_), k(s), out(out_stream) + { } void Init(mfem::TimeDependentOperator &f_) override; void Step(mfem::Vector &x, double &t, double &dt) override; - virtual ~ExplicitRRKSolver(); + // virtual ~ExplicitRRKSolver(); protected: int s; const double *a, *b, *c; mfem::Vector y; mfem::Vector x_new; - mfem::Vector *k; + std::vector k; std::ostream *out; }; @@ -157,9 +159,9 @@ class BlockJacobiPreconditioner : public mfem::Solver ~BlockJacobiPreconditioner(); /// Controls the ownership of the blocks - /// \note if nonzero, BlockJacobiPreconditioner will delete all blocks that - /// are set (non-NULL); the default value is zero. - int owns_blocks; + /// \note if true, BlockJacobiPreconditioner will delete all blocks that + /// are set (non-NULL); the default value is false. + bool owns_blocks; private: /// Number of Blocks diff --git a/src/common/ode.cpp b/src/common/ode.cpp index 5e315f6b..8e96fd01 100644 --- a/src/common/ode.cpp +++ b/src/common/ode.cpp @@ -133,7 +133,7 @@ mfem::Operator &getJacobian(TimeDependentResidual &residual, MachInputs input{{"state", work}}; auto *jac_free = dynamic_cast(residual.jac_.get()); - if (jac_free) + if (jac_free != nullptr) { // Using a Jacobian-free implementation jac_free->setScaling(dt); diff --git a/src/physics/electromagnetics/current_source_functions.cpp b/src/physics/electromagnetics/current_source_functions.cpp index 7585d828..e5ed6489 100644 --- a/src/physics/electromagnetics/current_source_functions.cpp +++ b/src/physics/electromagnetics/current_source_functions.cpp @@ -727,7 +727,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( auto n_slots = cached_inputs.at("n_slots"); cached_inputs.emplace("stack_length", 0.345); auto stack_length = cached_inputs.at("stack_length"); - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -762,7 +762,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( auto n_slots = cached_inputs.at("n_slots"); cached_inputs.emplace("stack_length", 0.345); auto stack_length = cached_inputs.at("stack_length"); - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -793,7 +793,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "x") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -816,7 +816,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "y") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -839,7 +839,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -862,7 +862,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "-z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -885,7 +885,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "ring") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -907,7 +907,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "box1") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -929,7 +929,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "box2") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -951,7 +951,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "team13") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -1057,7 +1057,7 @@ CurrentDensityCoefficient2D::CurrentDensityCoefficient2D( { if (source == "z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -1076,7 +1076,7 @@ CurrentDensityCoefficient2D::CurrentDensityCoefficient2D( } else if (source == "-z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { // source_coeffs.emplace(attr, mfem::FunctionCoefficient(-1.0)); source_coeffs.emplace( @@ -1096,7 +1096,7 @@ CurrentDensityCoefficient2D::CurrentDensityCoefficient2D( } else if (source == "box1") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace(attr, mfem::FunctionCoefficient( @@ -1117,7 +1117,7 @@ CurrentDensityCoefficient2D::CurrentDensityCoefficient2D( } else if (source == "box2") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace(attr, mfem::FunctionCoefficient( diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index b6248ba4..a9cfa789 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -4982,7 +4982,7 @@ void ForceIntegratorMeshSens::AssembleRHSElementVect( void setInputs(SteinmetzLossIntegrator &integ, const MachInputs &inputs) { setValueFromInputs(inputs, "frequency", integ.freq); - if (integ.name != "") + if (!integ.name.empty()) { setValueFromInputs( inputs, "max_flux_magnitude:" + integ.name, integ.max_flux_mag); @@ -5040,7 +5040,7 @@ void setInputs(SteinmetzLossDistributionIntegrator &integ, { setValueFromInputs(inputs, "frequency", integ.freq); - if (integ.name != "") + if (!integ.name.empty()) { setValueFromInputs( inputs, "max_flux_magnitude:" + integ.name, integ.max_flux_mag); diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index d40fda07..98f698e0 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -192,7 +192,7 @@ class CurlCurlNLFIntegratorStateFwdSens : public mfem::LinearFormIntegrator void AssembleRHSElementVect(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, - mfem::Vector &state_bar) override; + mfem::Vector &res_dot) override; private: /// the state to use when evaluating (dR/du) * state_dot @@ -593,7 +593,7 @@ class MagneticEnergyIntegrator : public mfem::NonlinearFormIntegrator void AssembleElementVector(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun, - mfem::Vector &elvect) override; + mfem::Vector &elfun_bar) override; private: /// material (thus mesh) dependent model describing reluctivity diff --git a/src/physics/electromagnetics/magnetic_source_functions.cpp b/src/physics/electromagnetics/magnetic_source_functions.cpp index 4113db6e..0e7b515e 100644 --- a/src/physics/electromagnetics/magnetic_source_functions.cpp +++ b/src/physics/electromagnetics/magnetic_source_functions.cpp @@ -441,7 +441,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( { if (source == "north") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -462,7 +462,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "south") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -483,7 +483,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "cw") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -504,7 +504,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "ccw") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -525,7 +525,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "x") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -546,7 +546,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "y") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, @@ -567,7 +567,7 @@ MagnetizationCoefficient::MagnetizationCoefficient( } else if (source == "z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( attr, diff --git a/src/physics/electromagnetics/reluctivity_coefficient.cpp b/src/physics/electromagnetics/reluctivity_coefficient.cpp index bbce9b53..5ff79297 100644 --- a/src/physics/electromagnetics/reluctivity_coefficient.cpp +++ b/src/physics/electromagnetics/reluctivity_coefficient.cpp @@ -334,7 +334,7 @@ ReluctivityCoefficient::ReluctivityCoefficient(const nlohmann::json &nu_options, : nu(std::make_unique(1.0 / mu_0)) { /// loop over all components, construct a reluctivity coefficient for each - for (auto &component : nu_options["components"]) + for (const auto &component : nu_options["components"]) { int attr = component.value("attr", -1); if (-1 != attr) @@ -344,7 +344,7 @@ ReluctivityCoefficient::ReluctivityCoefficient(const nlohmann::json &nu_options, } else { - for (auto &attribute : component["attrs"]) + for (const auto &attribute : component["attrs"]) { nu.addCoefficient(attribute, constructReluctivityCoeff(component, materials)); diff --git a/src/physics/fluidflow/flow_control_solver.cpp b/src/physics/fluidflow/flow_control_solver.cpp index 0075c412..4f483e86 100644 --- a/src/physics/fluidflow/flow_control_solver.cpp +++ b/src/physics/fluidflow/flow_control_solver.cpp @@ -415,8 +415,7 @@ template void FlowControlSolver::addOutput(const std::string &fun, const nlohmann::json &options) { - FlowControlResidual &flow_control_res = - getConcrete(*spatial_res); + auto &flow_control_res = getConcrete(*spatial_res); outputs.emplace(fun, flow_control_res.constructOutput(fun, options)); } diff --git a/src/physics/fluidflow/flow_solver.cpp b/src/physics/fluidflow/flow_solver.cpp index 26c2d63e..ccf61833 100644 --- a/src/physics/fluidflow/flow_solver.cpp +++ b/src/physics/fluidflow/flow_solver.cpp @@ -210,8 +210,7 @@ template void FlowSolver::addOutput(const std::string &fun, const nlohmann::json &options) { - FlowResidual &flow_res = - getConcrete>(*spatial_res); + auto &flow_res = getConcrete>(*spatial_res); outputs.emplace(fun, flow_res.constructOutput(fun, options)); } diff --git a/src/physics/mach_nonlinearform.cpp b/src/physics/mach_nonlinearform.cpp index 8adec254..0864b663 100644 --- a/src/physics/mach_nonlinearform.cpp +++ b/src/physics/mach_nonlinearform.cpp @@ -132,7 +132,7 @@ void setUpAdjointSystem(MachNonlinearForm &form, auto &jac_trans = getJacobianTranspose(form, inputs, "state"); adj_solver.SetOperator(jac_trans); - auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); + const auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); if (ess_tdof_list.Size() == 0) { return; diff --git a/src/physics/meshmove/mesh_move_integ.hpp b/src/physics/meshmove/mesh_move_integ.hpp index 6d40ed6e..2e254167 100644 --- a/src/physics/meshmove/mesh_move_integ.hpp +++ b/src/physics/meshmove/mesh_move_integ.hpp @@ -69,7 +69,7 @@ class ElasticityPositionIntegratorStateFwdSens void AssembleRHSElementVect(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, - mfem::Vector &state_bar) override; + mfem::Vector &res_dot) override; private: /// the state_dot to use when evaluating (dR/du) * state_dot diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index 2d3fbb15..1b394dec 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -21,7 +21,7 @@ double VolumeIntegrator::GetElementEnergy(const mfem::FiniteElement &el, trans.SetIntPoint(&ip); double val = ip.weight * trans.Weight(); - if (rho) + if (rho != nullptr) { val *= rho->Eval(trans, ip); } diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index e02cb723..c63cf2ff 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -349,7 +349,7 @@ PDESolver::PDESolver(MPI_Comm incomm, /// materials to the solver's known material library if (solver_options.contains("components")) { - for (auto &component : solver_options["components"]) + for (const auto &component : solver_options["components"]) { const auto &material = component["material"]; if (material.is_string()) @@ -377,10 +377,11 @@ PDESolver::PDESolver(MPI_Comm incomm, setUpExternalFields(); } -PDESolver::PDESolver(MPI_Comm incomm, - const nlohmann::json &solver_options, - std::function num_states, - std::unique_ptr smesh) +PDESolver::PDESolver( + MPI_Comm incomm, + const nlohmann::json &solver_options, + const std::function &num_states, + std::unique_ptr smesh) : AbstractSolver2(incomm, solver_options), mesh_(constructMesh(comm, options["mesh"], std::move(smesh))), materials(material_library) diff --git a/src/physics/pde_solver.hpp b/src/physics/pde_solver.hpp index 4304bdaa..c5b1634f 100644 --- a/src/physics/pde_solver.hpp +++ b/src/physics/pde_solver.hpp @@ -95,7 +95,7 @@ class PDESolver : public AbstractSolver2 /// available to determine the number of states. PDESolver(MPI_Comm incomm, const nlohmann::json &solver_options, - std::function num_states, + const std::function &num_states, std::unique_ptr smesh = nullptr); protected: diff --git a/src/physics/thermal/thermal_residual.cpp b/src/physics/thermal/thermal_residual.cpp index 9ad766cf..f0a393f2 100644 --- a/src/physics/thermal/thermal_residual.cpp +++ b/src/physics/thermal/thermal_residual.cpp @@ -133,7 +133,7 @@ ThermalResidual::ThermalResidual( if (options.contains("bcs")) { - auto &bcs = options["bcs"]; + const auto &bcs = options["bcs"]; // convection heat transfer boundary condition if (bcs.contains("convection")) diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index cea75487..45f39844 100644 --- a/src/utils/l2_transfer_operator.cpp +++ b/src/utils/l2_transfer_operator.cpp @@ -72,7 +72,7 @@ class ScalarIdentityOperator : public mach::L2TransferOperation mfem::Vector &mesh_coords_bar) const override { auto &isotrans = dynamic_cast(trans); - auto &mesh_fe = *isotrans.GetFE(); + const auto &mesh_fe = *isotrans.GetFE(); int space_dim = isotrans.GetSpaceDim(); int mesh_dof = mesh_fe.GetDof(); @@ -247,7 +247,7 @@ class IdentityOperator : public mach::L2TransferOperation mfem::Vector &mesh_coords_bar) const override { auto &isotrans = dynamic_cast(trans); - auto &mesh_fe = *isotrans.GetFE(); + const auto &mesh_fe = *isotrans.GetFE(); int space_dim = isotrans.GetSpaceDim(); int mesh_dof = mesh_fe.GetDof(); @@ -455,7 +455,7 @@ class CurlOperator : public mach::L2TransferOperation mfem::Vector &mesh_coords_bar) const override { auto &isotrans = dynamic_cast(trans); - auto &mesh_fe = *isotrans.GetFE(); + const auto &mesh_fe = *isotrans.GetFE(); int space_dim = isotrans.GetSpaceDim(); int curl_dim = space_dim == 3 ? 3 : 1; @@ -707,7 +707,7 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation mfem::Vector &mesh_coords_bar) const override { auto &isotrans = dynamic_cast(trans); - auto &mesh_fe = *isotrans.GetFE(); + const auto &mesh_fe = *isotrans.GetFE(); int state_dof = state_fe.GetDof(); int output_dof = output_fe.GetDof(); @@ -958,7 +958,7 @@ void L2TransferOperator::apply(const MachInputs &inputs, mfem::Vector &out_vec) el_output.SetSize(output_vdofs.Size()); state.gridFunc().GetSubVector(state_vdofs, el_state); - if (state_dof_trans) + if (state_dof_trans != nullptr) { state_dof_trans->InvTransformPrimal(el_state); } @@ -966,7 +966,7 @@ void L2TransferOperator::apply(const MachInputs &inputs, mfem::Vector &out_vec) /// apply the operation operation->apply(state_fe, output_fe, trans, el_state, el_output); - if (output_dof_trans) + if (output_dof_trans != nullptr) { output_dof_trans->TransformPrimal(el_output); } @@ -1011,13 +1011,13 @@ void L2TransferOperator::vectorJacobianProduct(const mfem::Vector &out_bar, el_state_bar.SetSize(state_vdofs.Size()); state.gridFunc().GetSubVector(state_vdofs, el_state); - if (state_dof_trans) + if (state_dof_trans != nullptr) { state_dof_trans->InvTransformPrimal(el_state); } output_adjoint.gridFunc().GetSubVector(output_adj_vdofs, el_output_adj); - if (output_adj_dof_trans) + if (output_adj_dof_trans != nullptr) { output_adj_dof_trans->InvTransformPrimal(el_output_adj); } @@ -1026,7 +1026,7 @@ void L2TransferOperator::vectorJacobianProduct(const mfem::Vector &out_bar, operation->apply_state_bar( state_fe, output_fe, trans, el_output_adj, el_state, el_state_bar); - if (state_dof_trans) + if (state_dof_trans != nullptr) { state_dof_trans->TransformDual(el_state_bar); } @@ -1070,13 +1070,13 @@ void L2TransferOperator::vectorJacobianProduct(const mfem::Vector &out_bar, el_mesh_coords_bar.SetSize(mesh_coords_vdofs.Size()); state.gridFunc().GetSubVector(state_vdofs, el_state); - if (state_dof_trans) + if (state_dof_trans != nullptr) { state_dof_trans->InvTransformPrimal(el_state); } output_adjoint.gridFunc().GetSubVector(output_adj_vdofs, el_output_adj); - if (output_adj_dof_trans) + if (output_adj_dof_trans != nullptr) { output_adj_dof_trans->InvTransformPrimal(el_output_adj); } @@ -1089,7 +1089,7 @@ void L2TransferOperator::vectorJacobianProduct(const mfem::Vector &out_bar, el_state, el_mesh_coords_bar); - if (mesh_coords_dof_trans) + if (mesh_coords_dof_trans != nullptr) { mesh_coords_dof_trans->TransformDual(el_mesh_coords_bar); } diff --git a/src/utils/mesh_warper/mesh_warper.cpp b/src/utils/mesh_warper/mesh_warper.cpp index ff7b3ea5..18a1298d 100644 --- a/src/utils/mesh_warper/mesh_warper.cpp +++ b/src/utils/mesh_warper/mesh_warper.cpp @@ -93,7 +93,7 @@ class MeshWarperResidual final /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; - std::unique_ptr constructPreconditioner( + static std::unique_ptr constructPreconditioner( mfem::ParFiniteElementSpace &fes, const nlohmann::json &prec_options) { @@ -131,7 +131,7 @@ void evaluate(MeshWarperResidual &residual, mfem::Vector state; setVectorFromInputs(inputs, "state", state); - auto &surface_indices = residual.surface_indices; + const auto &surface_indices = residual.surface_indices; for (int i = 0; i < surface_indices.Size(); ++i) { res_vec(surface_indices[i]) = @@ -182,7 +182,7 @@ void jacobianVectorProduct(MeshWarperResidual &residual, { if (wrt == "surf_mesh_coords") { - auto &surface_indices = residual.surface_indices; + const auto &surface_indices = residual.surface_indices; for (int i = 0; i < surface_indices.Size(); ++i) { res_dot(surface_indices[i]) -= wrt_dot(i); @@ -205,7 +205,7 @@ void vectorJacobianProduct(MeshWarperResidual &residual, { if (wrt == "surf_mesh_coords") { - auto &surface_indices = residual.surface_indices; + const auto &surface_indices = residual.surface_indices; for (int i = 0; i < surface_indices.Size(); ++i) { wrt_bar(i) -= res_bar(surface_indices[i]); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index eeb7a01d..0a4c3baf 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -811,9 +811,8 @@ unique_ptr buildQuarterAnnulusMesh(int degree, // fes does not own fec, which is generated in this function's scope, but // the grid function can own both the fec and fes - H1_FECollection *fec = new H1_FECollection(degree, 2 /* = dim */); - FiniteElementSpace *fes = - new FiniteElementSpace(&mesh, fec, 2, Ordering::byVDIM); + auto *fec = new H1_FECollection(degree, 2 /* = dim */); + auto *fes = new FiniteElementSpace(&mesh, fec, 2, Ordering::byVDIM); // This lambda function transforms from (r,\theta) space to (x,y) space auto xy_fun = [](const Vector &rt, Vector &xy) @@ -823,7 +822,7 @@ unique_ptr buildQuarterAnnulusMesh(int degree, xy(1) = (rt(0) + 1.0) * sin(rt(1)); }; VectorFunctionCoefficient xy_coeff(2, xy_fun); - GridFunction *xy = new GridFunction(fes); + auto *xy = new GridFunction(fes); xy->MakeOwner(fec); xy->ProjectCoefficient(xy_coeff); From 4a1adac5395d380955095a8567994e43cd90aa86 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 23 Aug 2022 19:18:29 -0400 Subject: [PATCH 36/72] make format --- src/common/linesearch.cpp | 3 ++- src/common/linesearch.hpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp index b4b24703..6448f813 100644 --- a/src/common/linesearch.cpp +++ b/src/common/linesearch.cpp @@ -103,7 +103,8 @@ double BacktrackingLineSearch::search(const std::function &phi, return alpha2; } -Phi::Phi(const std::function &calcRes, +Phi::Phi(const std::function + &calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, diff --git a/src/common/linesearch.hpp b/src/common/linesearch.hpp index e87109b1..8735d10a 100644 --- a/src/common/linesearch.hpp +++ b/src/common/linesearch.hpp @@ -38,7 +38,8 @@ class BacktrackingLineSearch : public LineSearch class Phi { public: - Phi(const std::function &calcRes, + Phi(const std::function + &calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, From e3b8a29367e2a13afbf346a24263319a61a19323 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 23 Aug 2022 19:58:10 -0400 Subject: [PATCH 37/72] change Phi to own a copy of the calcRes function instead of owning a reference to it to address address sanitizer error --- src/common/linesearch.cpp | 5 ++--- src/common/linesearch.hpp | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp index 6448f813..2370ac16 100644 --- a/src/common/linesearch.cpp +++ b/src/common/linesearch.cpp @@ -103,13 +103,12 @@ double BacktrackingLineSearch::search(const std::function &phi, return alpha2; } -Phi::Phi(const std::function - &calcRes, +Phi::Phi(std::function calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, mfem::Operator &jac) - : calcRes(calcRes), + : calcRes(std::move(calcRes)), state(state), descent_dir(descent_dir), scratch(state.Size()), diff --git a/src/common/linesearch.hpp b/src/common/linesearch.hpp index 8735d10a..2a795cc0 100644 --- a/src/common/linesearch.hpp +++ b/src/common/linesearch.hpp @@ -38,8 +38,7 @@ class BacktrackingLineSearch : public LineSearch class Phi { public: - Phi(const std::function - &calcRes, + Phi(std::function calcRes, const mfem::Vector &state, const mfem::Vector &descent_dir, mfem::Vector &residual, @@ -48,7 +47,7 @@ class Phi double operator()(double alpha); private: - const std::function &calcRes; + std::function calcRes; const mfem::Vector &state; const mfem::Vector &descent_dir; mfem::Vector scratch; From 6cd70bd8af2f8202d61498bc3383ce97bca587fb Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 24 Aug 2022 10:47:44 -0600 Subject: [PATCH 38/72] get all of mach passing clang-tidy checks --- .clang-tidy | 2 + CMakeLists.txt | 6 +-- src/common/abstract_solver.cpp | 18 +++----- src/common/abstract_solver.hpp | 5 +-- src/common/functional_output.hpp | 12 ++--- src/common/mach_linearform.hpp | 4 +- src/common/mach_residual.hpp | 4 +- src/common/matrix_operators.cpp | 4 +- src/common/matrix_operators.hpp | 19 ++++---- src/common/mfem_extensions.hpp | 15 ++++--- src/common/ode.hpp | 2 +- src/physics/electromagnetics/current_load.cpp | 1 - .../electromagnetics/electromag_integ.hpp | 17 +++++-- .../electromagnetics/electromag_outputs.cpp | 6 +-- src/physics/fluidflow/euler_fluxes.hpp | 6 ++- src/physics/fluidflow/euler_integ.hpp | 24 +++++----- src/physics/fluidflow/euler_integ_def.hpp | 2 +- .../fluidflow/flow_control_residual.cpp | 45 +++++++------------ .../fluidflow/flow_control_residual.hpp | 2 +- src/physics/fluidflow/flow_control_solver.hpp | 34 +++++++------- src/physics/fluidflow/flow_residual.cpp | 12 ++--- src/physics/fluidflow/flow_solver.hpp | 36 +++++++-------- src/physics/fluidflow/navier_stokes_integ.hpp | 38 ++++++++-------- src/physics/mach_nonlinearform.cpp | 1 - src/physics/pde_solver.hpp | 22 +++++---- src/physics/solver.cpp | 11 +---- src/physics/thermal/thermal_residual.hpp | 2 +- src/utils/irrotational_projector.cpp | 2 +- src/utils/mesh_warper/mesh_warper.hpp | 4 +- 29 files changed, 174 insertions(+), 182 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 302ac362..81b9938d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -26,10 +26,12 @@ Checks: 'clang-diagnostic*, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -modernize-avoid-c-arrays, + -modernize-use-default-member-init, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-avoid-const-params-in-decls, -readability-else-after-return, + -readability-identifier-length, -readability-magic-numbers' CheckOptions: diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c5da3de..a419d90b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,8 +171,8 @@ option(MACH_USE_CLANG_TIDY if (MACH_USE_CLANG_TIDY) set_target_properties(mach PROPERTIES - CXX_CLANG_TIDY "clang-tidy;--format-style=file;--extra-arg=--std=c++17" - # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++11" + CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" + # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" ) endif (MACH_USE_CLANG_TIDY) @@ -310,7 +310,7 @@ if (BUILD_PYTHON_WRAPPER) if (MACH_USE_CLANG_TIDY) set_target_properties(pyMach PROPERTIES - CXX_CLANG_TIDY "clang-tidy;--format-style=file;--extra-arg=--std=c++17" + CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++11" ) endif (MACH_USE_CLANG_TIDY) diff --git a/src/common/abstract_solver.cpp b/src/common/abstract_solver.cpp index 256f5ebc..57c5e1ee 100644 --- a/src/common/abstract_solver.cpp +++ b/src/common/abstract_solver.cpp @@ -126,9 +126,8 @@ void AbstractSolver2::solveForState(const MachInputs &inputs, nonlinear_solver->Mult(zero, state); /// log final state - for (auto &pair : loggers) + for (auto &[logger, options] : loggers) { - auto &logger = pair.first; logState(logger, state, "state", 1, 1.0, rank); } } @@ -166,9 +165,8 @@ void AbstractSolver2::solveForAdjoint(const MachInputs &inputs, adj_solver->Mult(work, adjoint); /// log final state - for (auto &pair : loggers) + for (auto &[logger, options] : loggers) { - auto &logger = pair.first; logState(logger, adjoint, "adjoint", 0, 0.0, rank); } } @@ -516,10 +514,8 @@ void AbstractSolver2::vectorJacobianProduct(const mfem::Vector &res_bar, void AbstractSolver2::initialHook(const mfem::Vector &state) { - for (auto &pair : loggers) + for (auto &[logger, options] : loggers) { - auto &logger = pair.first; - auto &options = pair.second; if (options.initial_state) { logState(logger, state, "state", 0, 0.0, rank); @@ -532,10 +528,8 @@ void AbstractSolver2::iterationHook(int iter, double dt, const mfem::Vector &state) { - for (auto &pair : loggers) + for (auto &[logger, options] : loggers) { - auto &logger = pair.first; - auto &options = pair.second; if (options.each_timestep) { logState(logger, state, "state", iter, t, rank); @@ -570,10 +564,8 @@ void AbstractSolver2::terminalHook(int iter, double t_final, const mfem::Vector &state) { - for (auto &pair : loggers) + for (auto &[logger, options] : loggers) { - auto &logger = pair.first; - auto &options = pair.second; if (options.final_state) { logState(logger, state, "state", iter, t_final, rank); diff --git a/src/common/abstract_solver.hpp b/src/common/abstract_solver.hpp index 80609652..ce7977c9 100644 --- a/src/common/abstract_solver.hpp +++ b/src/common/abstract_solver.hpp @@ -310,10 +310,9 @@ class AbstractSolver2 /// Optional data loggers that will save state vectors during timestepping std::vector loggers; - void addLogger(DataLogger logger, LoggingOptions &&options) + void addLogger(DataLogger logger, LoggingOptions options) { - loggers.emplace_back(std::make_pair( - std::move(logger), std::move(options))); + loggers.emplace_back(std::move(logger), options); } /// For code that should be executed before the time stepping begins diff --git a/src/common/functional_output.hpp b/src/common/functional_output.hpp index db122e63..0e82f7f5 100644 --- a/src/common/functional_output.hpp +++ b/src/common/functional_output.hpp @@ -68,7 +68,8 @@ class FunctionalOutput /// should be used on /// \tparam T - type of integrator, used for constructing MachIntegrator template - void addOutputDomainIntegrator(T *integrator, std::vector attr_marker); + void addOutputDomainIntegrator(T *integrator, + const std::vector &attr_marker); /// Adds interface integrator to the nonlinear form that backs this output, /// and adds a reference to it to in integs as a MachIntegrator @@ -94,7 +95,7 @@ class FunctionalOutput /// \tparam T - type of integrator, used for constructing MachIntegrator template void addOutputBdrFaceIntegrator(T *integrator, - std::vector bdr_attr_marker); + const std::vector &bdr_attr_marker); FunctionalOutput(mfem::ParFiniteElementSpace &fes, std::map &fields) @@ -146,8 +147,9 @@ void FunctionalOutput::addOutputDomainIntegrator(T *integrator) } template -void FunctionalOutput::addOutputDomainIntegrator(T *integrator, - std::vector attr_marker) +void FunctionalOutput::addOutputDomainIntegrator( + T *integrator, + const std::vector &attr_marker) { integs.emplace_back(*integrator); // auto &marker = domain_markers.emplace_back(attr_marker.size()); @@ -181,7 +183,7 @@ void FunctionalOutput::addOutputBdrFaceIntegrator(T *integrator) template void FunctionalOutput::addOutputBdrFaceIntegrator( T *integrator, - std::vector bdr_attr_marker) + const std::vector &bdr_attr_marker) { integs.emplace_back(*integrator); diff --git a/src/common/mach_linearform.hpp b/src/common/mach_linearform.hpp index ea28f228..b14d768d 100644 --- a/src/common/mach_linearform.hpp +++ b/src/common/mach_linearform.hpp @@ -59,7 +59,7 @@ class MachLinearForm final /// should be used on /// \tparam T - type of integrator, used for constructing MachIntegrator template - void addDomainIntegrator(T *integrator, std::vector attr_marker); + void addDomainIntegrator(T *integrator, const std::vector &attr_marker); /// Adds boundary integrator to linear form /// \param[in] integrator - linear form integrator for boundary @@ -164,7 +164,7 @@ void MachLinearForm::addDomainIntegrator(T *integrator) template void MachLinearForm::addDomainIntegrator(T *integrator, - std::vector attr_marker) + const std::vector &attr_marker) { integs.emplace_back(*integrator); diff --git a/src/common/mach_residual.hpp b/src/common/mach_residual.hpp index 6776b2b1..208761ca 100644 --- a/src/common/mach_residual.hpp +++ b/src/common/mach_residual.hpp @@ -67,7 +67,7 @@ double vectorJacobianProduct(T & /*unused*/, } template -void vectorJacobianProduct(T &, +void vectorJacobianProduct(T & /*unused*/, const mfem::Vector & /*unused*/, const std::string & /*unused*/, mfem::Vector & /*unused*/) @@ -104,7 +104,7 @@ mfem::Solver *getPreconditioner(T & /*unused*/) template mfem::Operator &getJacobianBlock(T & /*unused*/, const MachInputs & /*unused*/, - int) + int /*unused*/) { throw MachException( "getJacobianBlock not specialized for concrete residual type!\n"); diff --git a/src/common/matrix_operators.cpp b/src/common/matrix_operators.cpp index b8f258bd..520f60d5 100644 --- a/src/common/matrix_operators.cpp +++ b/src/common/matrix_operators.cpp @@ -59,12 +59,12 @@ void SumOfOperators::Mult(const Vector &x, Vector &y) const y += work_vec; } -JacobianFree::JacobianFree(MachResidual &residual) +JacobianFree::JacobianFree(MachResidual &residual, Operator *mat_explicit) : Operator(getSize(residual)), comm(getMPIComm(residual)), scale(1.0), res(residual), - explicit_part(nullptr), + explicit_part(mat_explicit), state(getSize(res)), res_at_state(getSize(res)), state_pert(getSize(res)) diff --git a/src/common/matrix_operators.hpp b/src/common/matrix_operators.hpp index bae4d963..6a4f6052 100644 --- a/src/common/matrix_operators.hpp +++ b/src/common/matrix_operators.hpp @@ -204,18 +204,19 @@ double JacobianFree::getStepSize(const mfem::Vector &baseline, class JacobianFree : public mfem::Operator { public: - /// Construct a Jacobian-free matrix-vector product operator - /// \param[in] residual - the equation/residual that defines the Jacobian - JacobianFree(MachResidual &residual); - /// Construct a Jacobian-free matrix-vector product operator /// \param[in] residual - the equation/residual that defines the Jacobian /// \param[in] mat_explicit - (optional) explicit part of the operator - JacobianFree(MachResidual &residual, mfem::Operator &mat_explicit) - : JacobianFree(residual) - { - explicit_part = &mat_explicit; - } + JacobianFree(MachResidual &residual, mfem::Operator *mat_explicit = nullptr); + + // /// Construct a Jacobian-free matrix-vector product operator + // /// \param[in] residual - the equation/residual that defines the Jacobian + // /// \param[in] mat_explicit - (optional) explicit part of the operator + // JacobianFree(MachResidual &residual, mfem::Operator &mat_explicit) + // : JacobianFree(residual) + // { + // explicit_part = &mat_explicit; + // } /// Sets the scaling applied to the Jacobian-free part of the operator void setScaling(double scaling) { scale = scaling; } diff --git a/src/common/mfem_extensions.hpp b/src/common/mfem_extensions.hpp index 7b931cca..08fdc6f5 100644 --- a/src/common/mfem_extensions.hpp +++ b/src/common/mfem_extensions.hpp @@ -107,6 +107,12 @@ class BlockJacobiPreconditioner : public mfem::Solver /// \param[in] offsets - mark the start of each row/column block BlockJacobiPreconditioner(const mfem::Array &offsets); + BlockJacobiPreconditioner(const BlockJacobiPreconditioner &) = delete; + BlockJacobiPreconditioner &operator=(const BlockJacobiPreconditioner &) = delete; + + BlockJacobiPreconditioner(BlockJacobiPreconditioner &&) noexcept = delete; + BlockJacobiPreconditioner &operator=(BlockJacobiPreconditioner &&) noexcept = delete; + /// Add a square block op in the block-entry (iblock, iblock) /// \param[in] iblock - the index of row-column block entry being set /// \param[in] op - the solver used to define the (iblock, iblock) entry @@ -114,7 +120,7 @@ class BlockJacobiPreconditioner : public mfem::Solver /// Calls SetOperator on the diagonal block operators /// \param[in] op - a BlockOperator whose diagonal entries are used - virtual void SetOperator(const mfem::Operator &op) override; + void SetOperator(const mfem::Operator &op) override; /// Return the number of blocks /// \returns the number of row/column blocks in the preconditioner @@ -147,16 +153,15 @@ class BlockJacobiPreconditioner : public mfem::Solver /// Operator application /// \param[in] x - the vector being preconditioned /// \param[in] y - the preconditioned vector - virtual void Mult(const mfem::Vector &x, mfem::Vector &y) const override; + void Mult(const mfem::Vector &x, mfem::Vector &y) const override; /// Action of the transpose operator /// \param[in] x - the vector being preconditioned /// \param[in] y - the preconditioned vector - virtual void MultTranspose(const mfem::Vector &x, - mfem::Vector &y) const override; + void MultTranspose(const mfem::Vector &x, mfem::Vector &y) const override; /// Preconditioner destructor - ~BlockJacobiPreconditioner(); + ~BlockJacobiPreconditioner() override; /// Controls the ownership of the blocks /// \note if true, BlockJacobiPreconditioner will delete all blocks that diff --git a/src/common/ode.hpp b/src/common/ode.hpp index d3820961..7cbd797c 100644 --- a/src/common/ode.hpp +++ b/src/common/ode.hpp @@ -80,7 +80,7 @@ class TimeDependentResidual final } else if (block_mass != nullptr) { - jac_ = std::make_unique(spatial_res_, *mass_matrix_); + jac_ = std::make_unique(spatial_res_, mass_matrix_); } } diff --git a/src/physics/electromagnetics/current_load.cpp b/src/physics/electromagnetics/current_load.cpp index ba6f3233..62ddfcc0 100644 --- a/src/physics/electromagnetics/current_load.cpp +++ b/src/physics/electromagnetics/current_load.cpp @@ -218,7 +218,6 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, /// project current coeff as initial guess for iterative solve j.ProjectCoefficient(current); - div_free_current_vec = 0.0; /// Create a H(curl) mass matrix for integrating grid functions nd_mass.AddDomainIntegrator(new VectorFEMassIntegrator); diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 98f698e0..23f9d130 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -2,6 +2,7 @@ #define MACH_ELECTROMAG_INTEG #include +#include #include "mfem.hpp" @@ -1010,7 +1011,7 @@ class DCLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator DCLossFunctionalDistributionIntegrator(mfem::Coefficient &sigma, std::string name = "") - : sigma(sigma), name(name) + : sigma(sigma), name(std::move(name)) { } private: @@ -1078,7 +1079,7 @@ class ACLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator ACLossFunctionalDistributionIntegrator(mfem::GridFunction &peak_flux, mfem::Coefficient &sigma, std::string name = "") - : peak_flux(peak_flux), sigma(sigma), name(name) + : peak_flux(peak_flux), sigma(sigma), name(std::move(name)) { } private: @@ -1380,7 +1381,11 @@ class SteinmetzLossIntegrator : public mfem::NonlinearFormIntegrator mfem::Coefficient &alpha, mfem::Coefficient &beta, std::string name = "") - : rho(rho), k_s(k_s), alpha(alpha), beta(beta), name(name) + : rho(rho), + k_s(k_s), + alpha(alpha), + beta(beta), + name(std::move(name)) { } private: @@ -1424,7 +1429,11 @@ class SteinmetzLossDistributionIntegrator : public mfem::LinearFormIntegrator mfem::Coefficient &alpha, mfem::Coefficient &beta, std::string name = "") - : rho(rho), k_s(k_s), alpha(alpha), beta(beta), name(name) + : rho(rho), + k_s(k_s), + alpha(alpha), + beta(beta), + name(std::move(name)) { } private: diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index ca7bb2e3..362d3434 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -19,7 +19,7 @@ void setOptions(ForceFunctional &output, const nlohmann::json &options) auto space_dim = output.fields.at("vforce").mesh().SpaceDimension(); mfem::VectorConstantCoefficient axis_vector( - mfem::Vector(&axis[0], space_dim)); + mfem::Vector(axis.data(), space_dim)); auto &v = output.fields.at("vforce").gridFunc(); v = 0.0; @@ -34,11 +34,11 @@ void setOptions(TorqueFunctional &output, const nlohmann::json &options) auto &&attrs = options["attributes"].get>(); auto &&axis = options["axis"].get>(); auto &&about = options["about"].get>(); - mfem::Vector axis_vector(&axis[0], axis.size()); + mfem::Vector axis_vector(axis.data(), axis.size()); axis_vector /= axis_vector.Norml2(); auto space_dim = output.fields.at("vtorque").mesh().SpaceDimension(); - mfem::Vector about_vector(&about[0], space_dim); + mfem::Vector about_vector(about.data(), space_dim); double r_data[3]; mfem::Vector r(r_data, space_dim); diff --git a/src/physics/fluidflow/euler_fluxes.hpp b/src/physics/fluidflow/euler_fluxes.hpp index f734ad69..86ecb6ad 100644 --- a/src/physics/fluidflow/euler_fluxes.hpp +++ b/src/physics/fluidflow/euler_fluxes.hpp @@ -198,7 +198,9 @@ inline xdouble entropy(const xdouble *q, const xdouble *qe) xdouble we[dim + 2]; calcEntropyVars(qe, we); for (int i = 0; i < dim + 2; ++i) + { ent -= we[i] * (q[i] - qe[i]); + } return ent; } @@ -750,7 +752,7 @@ void calcBoundaryFluxEC(const xdouble *dir, calcBoundaryFlux(dir, qbnd, q, w, flux); calcEntropyVars(q, w); // next, get the entropy flux difference - const xdouble psi = dot(q + 1, dir); + const auto psi = dot(q + 1, dir); // std::cout << "-----------------------------------------" << std::endl; // std::cout << "psi = " << psi << ": entflux = " << entflux << std::endl; // std::cout << "w^T f = " << dot(w, flux) << std::endl; @@ -784,7 +786,7 @@ void calcControlFlux(const xdouble *dir, { flux[i] = q[i] * U; } - xdouble press = pressure(q); + auto press = pressure(q); for (int i = 0; i < dim; ++i) { flux[i + 1] += dir[i] * press; diff --git a/src/physics/fluidflow/euler_integ.hpp b/src/physics/fluidflow/euler_integ.hpp index 914ccac0..7d9a0347 100644 --- a/src/physics/fluidflow/euler_integ.hpp +++ b/src/physics/fluidflow/euler_integ.hpp @@ -1,6 +1,8 @@ #ifndef MACH_EULER_INTEG #define MACH_EULER_INTEG +#include + #include "adept.h" #include "mfem.hpp" @@ -367,13 +369,13 @@ class FarFieldBC : public InviscidBoundaryIntegrator> /// \param[in] a - used to move residual to lhs (1.0) or rhs(-1.0) FarFieldBC(adept::Stack &diff_stack, const mfem::FiniteElementCollection *fe_coll, - const mfem::Vector &q_far, + mfem::Vector q_far, double a = 1.0) : InviscidBoundaryIntegrator>(diff_stack, fe_coll, dim + 2, a), - qfs(q_far), + qfs(std::move(q_far)), work_vec(dim + 2) { } @@ -449,7 +451,7 @@ class EntropyConserveBC fe_coll, dim + 2, a), - bc_fun(bnd_state), + bc_fun(std::move(bnd_state)), work1(dim + 2), work2(dim + 2) { } @@ -533,7 +535,7 @@ class ControlBC : public InviscidBoundaryIntegrator> ControlBC(adept::Stack &diff_stack, const mfem::FiniteElementCollection *fe_coll, BCScaleFun scale, - const mfem::Vector &xc, + mfem::Vector xc, double len = 1.0, double a = 1.0) : InviscidBoundaryIntegrator>(diff_stack, @@ -541,9 +543,9 @@ class ControlBC : public InviscidBoundaryIntegrator> dim + 2, a), len_scale(len), - x_actuator(xc), + x_actuator(std::move(xc)), control(0.0), - control_scale(scale), + control_scale(std::move(scale)), work1(dim + 2), work2(dim + 2) { } @@ -691,12 +693,12 @@ class PressureForce /// \param[in] force_dir - unit vector specifying the direction of the force PressureForce(adept::Stack &diff_stack, const mfem::FiniteElementCollection *fe_coll, - const mfem::Vector &force_dir) + mfem::Vector force_dir) : InviscidBoundaryIntegrator>(diff_stack, fe_coll, dim + 2, 1.0), - force_nrm(force_dir), + force_nrm(std::move(force_dir)), work_vec(dim + 2) { } @@ -803,15 +805,15 @@ class BoundaryEntropy BoundaryEntropy(adept::Stack &diff_stack, const mfem::FiniteElementCollection *fe_coll, BCScaleFun scale, - const mfem::Vector &xc, + mfem::Vector xc, double len = 1.0) : InviscidBoundaryIntegrator>(diff_stack, fe_coll, dim + 2, 1.0), len_scale(len), - x_actuator(xc), - control_scale(scale) + x_actuator(std::move(xc)), + control_scale(std::move(scale)) { } /// Returns the entropy, weighted by given scalar function, at a given point diff --git a/src/physics/fluidflow/euler_integ_def.hpp b/src/physics/fluidflow/euler_integ_def.hpp index 5c9e197d..12eec566 100644 --- a/src/physics/fluidflow/euler_integ_def.hpp +++ b/src/physics/fluidflow/euler_integ_def.hpp @@ -970,7 +970,7 @@ double BoundaryEntropy::calcBndryFun(const mfem::Vector &x, const mfem::Vector &q) { // evaluate the entropy, then return the scaled value - double S = entropy(q.GetData()); + auto S = entropy(q.GetData()); double scale = control_scale(len_scale, x_actuator, x); double dA = sqrt(dot(dir.GetData(), dir.GetData())); return scale * S * dA; diff --git a/src/physics/fluidflow/flow_control_residual.cpp b/src/physics/fluidflow/flow_control_residual.cpp index a574d458..5cac8687 100644 --- a/src/physics/fluidflow/flow_control_residual.cpp +++ b/src/physics/fluidflow/flow_control_residual.cpp @@ -14,16 +14,16 @@ namespace mach { ControlResidual::ControlResidual(MPI_Comm incomm, const nlohmann::json &control_options) + : Kp(0.0), + Td(0.0), + Ti(0.0), + beta(0.0), + eta(0.0), + target_entropy(0.0), + closed_loop(true), + time(0.0), + boundary_entropy(0.0) { - Kp = 0.0; - Td = 0.0; - Ti = 0.0; - eta = 0.0; - beta = 0.0; - time = 0.0; - target_entropy = 0.0; - closed_loop = true; - boundary_entropy = 0.0; MPI_Comm_dup(incomm, &comm); MPI_Comm_rank(comm, &rank); rank == 0 ? num_var = 2 : num_var = 0; @@ -40,14 +40,7 @@ ControlResidual::ControlResidual(MPI_Comm incomm, (*mass_mat)(0, 0) = 1.0; (*mass_mat)(1, 1) = 1.0; } - if (control_options["test-ode"]) - { - test_ode = true; - } - else - { - test_ode = false; - } + test_ode = control_options["test-ode"]; } void setInputs(ControlResidual &residual, const MachInputs &inputs) @@ -65,14 +58,8 @@ void setInputs(ControlResidual &residual, const MachInputs &inputs) setValueFromInputs(inputs, "target-entropy", residual.target_entropy); double closed_double = 1.0; setValueFromInputs(inputs, "closed-loop", closed_double); - if (fabs(closed_double) < numeric_limits::epsilon()) - { - residual.closed_loop = false; - } - else - { - residual.closed_loop = true; - } + residual.closed_loop = + fabs(closed_double) >= numeric_limits::epsilon(); // if (residual.rank == 0) // { @@ -160,7 +147,7 @@ void evaluate(ControlResidual &residual, Operator &getJacobian(ControlResidual &residual, const MachInputs &inputs, - std::string wrt) + const std::string &wrt) { setInputs(residual, inputs); if (residual.rank == 0) @@ -283,7 +270,7 @@ FlowControlResidual::FlowControlResidual( (*offsets)[2] = (*offsets)[1] + num_flow(); // create the mass operator mass_mat = make_unique(*offsets); - auto control_mass = getMassMatrix(control_res, options); + auto *control_mass = getMassMatrix(control_res, options); auto flow_mass = getMassMatrix(flow_res, options); mass_mat->SetDiagonalBlock(0, control_mass); mass_mat->SetDiagonalBlock(1, flow_mass); @@ -392,7 +379,9 @@ double FlowControlResidual::calcEntropyChange_( { // extract flow and control states to compute entropy extractStates(inputs, control_ref, flow_ref); - Vector dxdt, control_dxdt, flow_dxdt; + Vector dxdt; + Vector control_dxdt; + Vector flow_dxdt; setVectorFromInputs(inputs, "state_dot", dxdt, false, true); extractStates(dxdt, control_dxdt, flow_dxdt); diff --git a/src/physics/fluidflow/flow_control_residual.hpp b/src/physics/fluidflow/flow_control_residual.hpp index fb3af74d..2f325381 100644 --- a/src/physics/fluidflow/flow_control_residual.hpp +++ b/src/physics/fluidflow/flow_control_residual.hpp @@ -56,7 +56,7 @@ class ControlResidual final /// \note the underlying `Operator` is owned by `residual` friend mfem::Operator &getJacobian(ControlResidual &residual, const MachInputs &inputs, - std::string wrt); + const std::string &wrt); /// Evaluate the entropy/Lyapunov functional at the given state /// \param[inout] residual - passive-control residual whose entropy we want diff --git a/src/physics/fluidflow/flow_control_solver.hpp b/src/physics/fluidflow/flow_control_solver.hpp index 85c565e8..38e0c089 100644 --- a/src/physics/fluidflow/flow_control_solver.hpp +++ b/src/physics/fluidflow/flow_control_solver.hpp @@ -107,17 +107,17 @@ class FlowControlSolver : public AbstractSolver2 /// For code that should be executed before the time stepping begins /// \param[in] state - the current state - virtual void initialHook(const mfem::Vector &state) override final; + void initialHook(const mfem::Vector &state) final; /// For code that should be executed before `ode_solver->Step` /// \param[in] iter - the current iteration /// \param[in] t - the current time (before the step) /// \param[in] dt - the step size that will be taken /// \param[in] state - the current state - virtual void iterationHook(int iter, - double t, - double dt, - const mfem::Vector &state) override final; + void iterationHook(int iter, + double t, + double dt, + const mfem::Vector &state) final; /// Find the step size based on the options; e.g. for constant CFL or PTC /// \param[in] iter - the current iteration @@ -131,11 +131,11 @@ class FlowControlSolver : public AbstractSolver2 /// between nodes for the length in the CFL number. /// \note If the "steady" option is true, the time step will increase based /// on the baseline value of "dt" and the residual norm. - virtual double calcStepSize(int iter, - double t, - double t_final, - double dt_old, - const mfem::Vector &state) const override final; + double calcStepSize(int iter, + double t, + double t_final, + double dt_old, + const mfem::Vector &state) const final; /// Determines when to exit the time stepping loop /// \param[in] iter - the current iteration @@ -145,19 +145,17 @@ class FlowControlSolver : public AbstractSolver2 /// \param[in] state - the current state /// \note If a steady problem is being solved, the "steady-abstol" and /// "steady-reltol" options from "time-dis" to determine convergence. - virtual bool iterationExit(int iter, - double t, - double t_final, - double dt, - const mfem::Vector &state) const override final; + bool iterationExit(int iter, + double t, + double t_final, + double dt, + const mfem::Vector &state) const final; /// For code that should be executed after the time stepping ends /// \param[in] iter - the terminal iteration /// \param[in] t_final - the final time /// \param[in] state - the current state - virtual void terminalHook(int iter, - double t_final, - const mfem::Vector &state) override final; + void terminalHook(int iter, double t_final, const mfem::Vector &state) final; }; } // namespace mach diff --git a/src/physics/fluidflow/flow_residual.cpp b/src/physics/fluidflow/flow_residual.cpp index 3aab022a..dfd918a5 100644 --- a/src/physics/fluidflow/flow_residual.cpp +++ b/src/physics/fluidflow/flow_residual.cpp @@ -106,13 +106,13 @@ FlowResidual::FlowResidual( "FlowResidual::addFlowIntegrators: options must" "contain flow-param and space-dis!\n"); } - nlohmann::json flow = options["flow-param"]; - nlohmann::json space_dis = options["space-dis"]; + const nlohmann::json &flow = options["flow-param"]; + const nlohmann::json &space_dis = options["space-dis"]; addFlowDomainIntegrators(flow, space_dis); addFlowInterfaceIntegrators(flow, space_dis); if (options.contains("bcs")) { - nlohmann::json bcs = options["bcs"]; + const nlohmann::json &bcs = options["bcs"]; addFlowBoundaryIntegrators(flow, space_dis, bcs); } @@ -139,7 +139,7 @@ void FlowResidual::addFlowDomainIntegrators( const nlohmann::json &flow, const nlohmann::json &space_dis) { - auto flux = space_dis["flux-fun"]; + const auto &flux = space_dis["flux-fun"]; if (flux == "IR") { res.addDomainIntegrator(new IsmailRoeIntegrator(stack)); @@ -155,7 +155,7 @@ void FlowResidual::addFlowDomainIntegrators( res.addDomainIntegrator(new EulerIntegrator(stack)); } // add the LPS stabilization, if necessary - auto lps_coeff = space_dis["lps-coeff"]; + const auto &lps_coeff = space_dis["lps-coeff"]; if (lps_coeff > 0.0) { res.addDomainIntegrator( @@ -185,7 +185,7 @@ void FlowResidual::addFlowInterfaceIntegrators( // add the integrators based on if discretization is continuous or discrete if (space_dis["basis-type"] == "dsbp") { - auto iface_coeff = space_dis["iface-coeff"]; + const auto &iface_coeff = space_dis["iface-coeff"]; res.addInteriorFaceIntegrator(new InterfaceIntegrator( stack, iface_coeff, fes.FEColl())); if (flow["viscous"]) diff --git a/src/physics/fluidflow/flow_solver.hpp b/src/physics/fluidflow/flow_solver.hpp index 58328cc8..e5ed91eb 100644 --- a/src/physics/fluidflow/flow_solver.hpp +++ b/src/physics/fluidflow/flow_solver.hpp @@ -56,25 +56,25 @@ class FlowSolver : public PDESolver /// For code that should be executed before the time stepping begins /// \param[in] state - the current state - virtual void derivedPDEInitialHook(const mfem::Vector &state) override; + void derivedPDEInitialHook(const mfem::Vector &state) override; /// Code that should be executed each time step, before `ode_solver->Step` /// \param[in] iter - the current iteration /// \param[in] t - the current time (before the step) /// \param[in] dt - the step size that will be taken /// \param[in] state - the current state - virtual void derivedPDEIterationHook(int iter, - double t, - double dt, - const mfem::Vector &state) override; + void derivedPDEIterationHook(int iter, + double t, + double dt, + const mfem::Vector &state) override; /// Code that should be executed after time stepping ends /// \param[in] iter - the terminal iteration /// \param[in] t_final - the final time /// \param[in] state - the current state - virtual void derivedPDETerminalHook(int iter, - double t_final, - const mfem::Vector &state) override; + void derivedPDETerminalHook(int iter, + double t_final, + const mfem::Vector &state) override; /// Find the step size based on the options; e.g. for constant CFL or PTC /// \param[in] iter - the current iteration @@ -88,11 +88,11 @@ class FlowSolver : public PDESolver /// between nodes for the length in the CFL number. /// \note If the "steady" option is true, the time step will increase based /// on the baseline value of "dt" and the residual norm. - virtual double calcStepSize(int iter, - double t, - double t_final, - double dt_old, - const mfem::Vector &state) const override; + double calcStepSize(int iter, + double t, + double t_final, + double dt_old, + const mfem::Vector &state) const override; /// Determines when to exit the time stepping loop /// \param[in] iter - the current iteration @@ -102,11 +102,11 @@ class FlowSolver : public PDESolver /// \param[in] state - the current state /// \note If a steady problem is being solved, the "steady-abstol" and /// "steady-reltol" options from "time-dis" to determine convergence. - virtual bool iterationExit(int iter, - double t, - double t_final, - double dt, - const mfem::Vector &state) const override; + bool iterationExit(int iter, + double t, + double t_final, + double dt, + const mfem::Vector &state) const override; /// Add output @a fun based on @a options void addOutput(const std::string &fun, diff --git a/src/physics/fluidflow/navier_stokes_integ.hpp b/src/physics/fluidflow/navier_stokes_integ.hpp index 2adb1eb9..55fcfeab 100644 --- a/src/physics/fluidflow/navier_stokes_integ.hpp +++ b/src/physics/fluidflow/navier_stokes_integ.hpp @@ -1,6 +1,8 @@ #ifndef MACH_NAVIER_STOKES_INTEG #define MACH_NAVIER_STOKES_INTEG +#include + #include "adept.h" #include "mfem.hpp" @@ -167,7 +169,7 @@ class NoSlipAdiabaticWallBC const mfem::FiniteElementCollection *fe_coll, double Re_num, double Pr_num, - const mfem::Vector &q_ref, + mfem::Vector q_ref, double vis = -1.0, double a = 1.0) : ViscousBoundaryIntegrator>(diff_stack, @@ -177,7 +179,7 @@ class NoSlipAdiabaticWallBC Re(Re_num), Pr(Pr_num), mu(vis), - qfs(q_ref), + qfs(std::move(q_ref)), work_vec(dim + 2) { } @@ -461,7 +463,7 @@ class ViscousInflowBC : public ViscousBoundaryIntegrator> const mfem::FiniteElementCollection *fe_coll, double Re_num, double Pr_num, - const mfem::Vector &q_inflow, + mfem::Vector q_inflow, double vis = -1.0, double a = 1.0) : ViscousBoundaryIntegrator>(diff_stack, @@ -471,7 +473,7 @@ class ViscousInflowBC : public ViscousBoundaryIntegrator> Re(Re_num), Pr(Pr_num), mu(vis), - q_in(q_inflow), + q_in(std::move(q_inflow)), work_vec(dim + 2) { } @@ -610,7 +612,7 @@ class ViscousOutflowBC : public ViscousBoundaryIntegrator> const mfem::FiniteElementCollection *fe_coll, double Re_num, double Pr_num, - const mfem::Vector &q_outflow, + mfem::Vector q_outflow, double vis = -1.0, double a = 1.0) : ViscousBoundaryIntegrator>(diff_stack, @@ -620,7 +622,7 @@ class ViscousOutflowBC : public ViscousBoundaryIntegrator> Re(Re_num), Pr(Pr_num), mu(vis), - q_out(q_outflow), + q_out(std::move(q_outflow)), work_vec(dim + 2) { } @@ -759,7 +761,7 @@ class ViscousFarFieldBC const mfem::FiniteElementCollection *fe_coll, double Re_num, double Pr_num, - const mfem::Vector &q_far, + mfem::Vector q_far, double vis = -1.0, double a = 1.0) : ViscousBoundaryIntegrator>(diff_stack, @@ -769,7 +771,7 @@ class ViscousFarFieldBC Re(Re_num), Pr(Pr_num), mu(vis), - qfs(q_far), + qfs(std::move(q_far)), work_vec(dim + 2) { } @@ -927,7 +929,7 @@ class ViscousExactBC : public ViscousBoundaryIntegrator> Re(Re_num), Pr(Pr_num), mu(vis), - exactSolution(fun), + exactSolution(std::move(fun)), qexact(dim + 2), work_vec(dim + 2) { } @@ -1072,9 +1074,9 @@ class ViscousControlBC : public ViscousBoundaryIntegrator> const mfem::FiniteElementCollection *fe_coll, double Re_num, double Pr_num, - const mfem::Vector &q_ref, + mfem::Vector q_ref, BCScaleFun scale, - const mfem::Vector &xc, + mfem::Vector xc, double len = 1.0, double vis = -1.0, double a = 1.0) @@ -1085,11 +1087,11 @@ class ViscousControlBC : public ViscousBoundaryIntegrator> Re(Re_num), Pr(Pr_num), mu(vis), - qfs(q_ref), + qfs(std::move(q_ref)), len_scale(len), - x_actuator(xc), + x_actuator(std::move(xc)), control(0.0), - control_scale(scale), + control_scale(std::move(scale)), work_vec(dim + 2) { } @@ -1246,8 +1248,8 @@ class SurfaceForce : public mfem::NonlinearFormIntegrator int num_state_vars, double Re_num, double Pr_num, - const mfem::Vector &q_ref, - const mfem::Vector &force_dir, + mfem::Vector q_ref, + mfem::Vector force_dir, double vis = -1.0, double a = 1.0) : num_states(num_state_vars), @@ -1257,8 +1259,8 @@ class SurfaceForce : public mfem::NonlinearFormIntegrator Re(Re_num), Pr(Pr_num), mu(vis), - qfs(q_ref), - force_nrm(force_dir), + qfs(std::move(q_ref)), + force_nrm(std::move(force_dir)), work_vec(dim + 2) { } diff --git a/src/physics/mach_nonlinearform.cpp b/src/physics/mach_nonlinearform.cpp index 0864b663..f892b032 100644 --- a/src/physics/mach_nonlinearform.cpp +++ b/src/physics/mach_nonlinearform.cpp @@ -2,7 +2,6 @@ #include "mfem.hpp" -#include "utils.hpp" #include "mach_input.hpp" #include "mach_integrator.hpp" #include "mach_nonlinearform.hpp" diff --git a/src/physics/pde_solver.hpp b/src/physics/pde_solver.hpp index c5b1634f..9b9a1d52 100644 --- a/src/physics/pde_solver.hpp +++ b/src/physics/pde_solver.hpp @@ -47,8 +47,8 @@ struct MachMesh MachMesh(const MachMesh &) = delete; MachMesh &operator=(const MachMesh &) = delete; - MachMesh(MachMesh &&) noexcept; - MachMesh &operator=(MachMesh &&) noexcept; + MachMesh(MachMesh && /*other*/) noexcept; + MachMesh &operator=(MachMesh && /*other*/) noexcept; ~MachMesh(); #endif @@ -103,8 +103,8 @@ class PDESolver : public AbstractSolver2 MachMesh mesh_; /// Reference to solver state vector - mfem::ParMesh &mesh() { return *mesh_.mesh; } - const mfem::ParMesh &mesh() const { return *mesh_.mesh; } + mfem::ParMesh &mesh() const { return *mesh_.mesh; } + // const mfem::ParMesh &mesh() const { return *mesh_.mesh; } /// solver material properties nlohmann::json materials; @@ -146,7 +146,7 @@ class PDESolver : public AbstractSolver2 /// client overwrites this definition; however, there is a call to the /// virtual function derivedPDEinitialHook(state) that the client can /// overwrite. - virtual void initialHook(const mfem::Vector &state) override final; + void initialHook(const mfem::Vector &state) final; /// Code in a derived class that should be executed before time-stepping /// \param[in] state - the current state @@ -159,10 +159,10 @@ class PDESolver : public AbstractSolver2 /// \param[in] state - the current state /// \note This is `final` because we want to ensure that /// AbstractSolver2::iterationHook() is called. - virtual void iterationHook(int iter, - double t, - double dt, - const mfem::Vector &state) override final; + void iterationHook(int iter, + double t, + double dt, + const mfem::Vector &state) final; /// Code in a derived class that should be executed each time step /// \param[in] iter - the current iteration @@ -179,9 +179,7 @@ class PDESolver : public AbstractSolver2 /// \param[in] iter - the terminal iteration /// \param[in] t_final - the final time /// \param[in] state - the current state - virtual void terminalHook(int iter, - double t_final, - const mfem::Vector &state) override final; + void terminalHook(int iter, double t_final, const mfem::Vector &state) final; /// Code in a derived class that should be executed after time stepping ends /// \param[in] iter - the terminal iteration diff --git a/src/physics/solver.cpp b/src/physics/solver.cpp index aef323af..b563ff28 100644 --- a/src/physics/solver.cpp +++ b/src/physics/solver.cpp @@ -29,7 +29,6 @@ #include "utils.hpp" #include "mfem_extensions.hpp" #include "default_options.hpp" -#include "solver.hpp" #include "sbp_fe.hpp" #include "evolver.hpp" #include "diag_mass_integ.hpp" @@ -37,12 +36,6 @@ #include "mach_input.hpp" #include "mach_integrator.hpp" #include "mach_load.hpp" -#include "solver.hpp" -#include "utils.hpp" - -#ifdef MFEM_USE_EGADS -#include "gmi_egads.h" -#endif using namespace std; using namespace mfem; @@ -406,7 +399,7 @@ void AbstractSolver::constructPumiMesh() } pumi_mesh->end(it); - mesh.reset(new ParPumiMesh(comm, pumi_mesh.get())); + mesh = std::make_unique(comm, pumi_mesh.get()); // it = pumi_mesh->begin(pumi_mesh->getDimension()); // count = 0; @@ -1121,7 +1114,7 @@ std::unique_ptr AbstractSolver::getField( auto &field_gf = res_fields.at(name); auto *field_fes = field_gf.ParFESpace(); - auto field = std::unique_ptr(new HypreParVector(field_fes)); + auto field = std::make_unique(field_fes); field_gf.GetTrueDofs(*field); return field; } diff --git a/src/physics/thermal/thermal_residual.hpp b/src/physics/thermal/thermal_residual.hpp index 4f2a60a3..6464b7e1 100644 --- a/src/physics/thermal/thermal_residual.hpp +++ b/src/physics/thermal/thermal_residual.hpp @@ -89,7 +89,7 @@ class ThermalResidual final /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; - std::unique_ptr constructPreconditioner( + static std::unique_ptr constructPreconditioner( mfem::ParFiniteElementSpace &fes, const nlohmann::json &prec_options) { diff --git a/src/utils/irrotational_projector.cpp b/src/utils/irrotational_projector.cpp index afc53a2a..28fb78b1 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -21,7 +21,7 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, div_x(&h1_fes), pcg(h1_fes.GetComm()) { - psi = 0.0; + psi = 0.0; // NOLINT amg.SetPrintLevel(0); pcg.SetRelTol(1e-14); diff --git a/src/utils/mesh_warper/mesh_warper.hpp b/src/utils/mesh_warper/mesh_warper.hpp index d4ae5448..c29dbb28 100644 --- a/src/utils/mesh_warper/mesh_warper.hpp +++ b/src/utils/mesh_warper/mesh_warper.hpp @@ -36,8 +36,8 @@ class MeshWarper : public AbstractSolver2 MachMesh mesh_; /// Reference to solver state vector - mfem::ParMesh &mesh() { return *mesh_.mesh; } - const mfem::ParMesh &mesh() const { return *mesh_.mesh; } + mfem::ParMesh &mesh() const { return *mesh_.mesh; } + // const mfem::ParMesh &mesh() const { return *mesh_.mesh; } /// Members associated with fields /// Map of all state vectors used by the solver From 10ea296095c663fbacc976bfbc572d570c4a5452 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 24 Aug 2022 11:41:09 -0600 Subject: [PATCH 39/72] remove my MPI include from clang tidy command --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a419d90b..5decfcc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,8 +171,8 @@ option(MACH_USE_CLANG_TIDY if (MACH_USE_CLANG_TIDY) set_target_properties(mach PROPERTIES - CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" - # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" + CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17" + # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++17;" ) endif (MACH_USE_CLANG_TIDY) @@ -310,7 +310,7 @@ if (BUILD_PYTHON_WRAPPER) if (MACH_USE_CLANG_TIDY) set_target_properties(pyMach PROPERTIES - CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17;--extra-arg=-I/Users/tuckerbabcock/Developer/spack/opt/spack/darwin-monterey-m1/clang-14.0.5/openmpi-4.1.4-d4tmz3vj5doddhkjsuwy4zeansaxk7n7/include" + CXX_CLANG_TIDY "clang-tidy;--fix;--format-style=file;--extra-arg=--std=c++17" # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++11" ) endif (MACH_USE_CLANG_TIDY) From e2fd4aee66e8d2394a0967a3e452ae84b5e1c239 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 24 Aug 2022 11:41:34 -0600 Subject: [PATCH 40/72] make format --- src/common/mfem_extensions.hpp | 6 ++++-- src/physics/electromagnetics/electromag_integ.hpp | 12 ++---------- src/utils/irrotational_projector.cpp | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/common/mfem_extensions.hpp b/src/common/mfem_extensions.hpp index 08fdc6f5..413f01ef 100644 --- a/src/common/mfem_extensions.hpp +++ b/src/common/mfem_extensions.hpp @@ -108,10 +108,12 @@ class BlockJacobiPreconditioner : public mfem::Solver BlockJacobiPreconditioner(const mfem::Array &offsets); BlockJacobiPreconditioner(const BlockJacobiPreconditioner &) = delete; - BlockJacobiPreconditioner &operator=(const BlockJacobiPreconditioner &) = delete; + BlockJacobiPreconditioner &operator=(const BlockJacobiPreconditioner &) = + delete; BlockJacobiPreconditioner(BlockJacobiPreconditioner &&) noexcept = delete; - BlockJacobiPreconditioner &operator=(BlockJacobiPreconditioner &&) noexcept = delete; + BlockJacobiPreconditioner &operator=(BlockJacobiPreconditioner &&) noexcept = + delete; /// Add a square block op in the block-entry (iblock, iblock) /// \param[in] iblock - the index of row-column block entry being set diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 23f9d130..f0533f36 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1381,11 +1381,7 @@ class SteinmetzLossIntegrator : public mfem::NonlinearFormIntegrator mfem::Coefficient &alpha, mfem::Coefficient &beta, std::string name = "") - : rho(rho), - k_s(k_s), - alpha(alpha), - beta(beta), - name(std::move(name)) + : rho(rho), k_s(k_s), alpha(alpha), beta(beta), name(std::move(name)) { } private: @@ -1429,11 +1425,7 @@ class SteinmetzLossDistributionIntegrator : public mfem::LinearFormIntegrator mfem::Coefficient &alpha, mfem::Coefficient &beta, std::string name = "") - : rho(rho), - k_s(k_s), - alpha(alpha), - beta(beta), - name(std::move(name)) + : rho(rho), k_s(k_s), alpha(alpha), beta(beta), name(std::move(name)) { } private: diff --git a/src/utils/irrotational_projector.cpp b/src/utils/irrotational_projector.cpp index 28fb78b1..366b9519 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -21,7 +21,7 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, div_x(&h1_fes), pcg(h1_fes.GetComm()) { - psi = 0.0; // NOLINT + psi = 0.0; // NOLINT amg.SetPrintLevel(0); pcg.SetRelTol(1e-14); From ba4fdd38a9a62f7e9f3ee7a1779ff71c3d36a200 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 31 Aug 2022 13:41:08 -0400 Subject: [PATCH 41/72] differentiated NonlinearDiffusionIntegrator wrt mesh coords --- .../electromagnetics/electromag_integ.cpp | 172 +++++++++++++++++- .../electromagnetics/electromag_integ.hpp | 39 ++++ test/unit/test_electromag_integ.cpp | 93 +++++++++- 3 files changed, 294 insertions(+), 10 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index a9cfa789..7ec5834c 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -256,6 +256,175 @@ void NonlinearDiffusionIntegrator::AssembleElementGrad( } } +void NonlinearDiffusionIntegratorMeshRevSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + /// get the proper element, transformation, and state vector +#ifdef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector elfun; + mfem::Vector psi; +#endif + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + + dof_tr = adjoint.FESpace()->GetElementVDofs(element, vdofs); + adjoint.GetSubVector(vdofs, psi); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(psi); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix dshapedxt; + DenseMatrix dshapedxt_bar; + DenseMatrix PointMat_bar; +#else + auto &dshape = integ.dshape; + auto &dshapedxt = integ.dshapedxt; +#endif + + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + dshapedxt_bar.SetSize(ndof, space_dim); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + double pointflux_buffer[3] = {}; + Vector pointflux(pointflux_buffer, space_dim); + double pointflux_bar_buffer[3] = {}; + Vector pointflux_bar(pointflux_bar_buffer, space_dim); + + double curl_psi_buffer[3] = {}; + Vector curl_psi(curl_psi_buffer, dim); + double curl_psi_bar_buffer[3] = {}; + Vector curl_psi_bar(curl_psi_bar_buffer, dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(mesh_trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &alpha = integ.alpha; + auto &model = integ.model; + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + + /// holds quadrature weight + double w = alpha * ip.weight / trans_weight; + + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + dshapedxt.MultTranspose(elfun, pointflux); + dshapedxt.MultTranspose(psi, curl_psi); + const double curl_psi_dot_pointflux = curl_psi * pointflux; + + const double pointflux_norm = pointflux.Norml2(); + const double pointflux_mag = pointflux_norm / trans_weight; + + double model_val = model.Eval(trans, ip, pointflux_mag); + + /// dummy functional for adjoint-weighted residual + // fun += model_val * curl_psi_dot_pointflux * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun += model_val * curl_psi_dot_pointflux * w; + double model_val_bar = fun_bar * curl_psi_dot_pointflux * w; + double curl_psi_dot_pointflux_bar = fun_bar * model_val * w; + double w_bar = fun_bar * model_val * curl_psi_dot_pointflux; + + /// double model_val = model.Eval(trans, ip, pointflux_mag); + double pointflux_mag_bar = 0.0; + const double dmodeldpointflux_mag = model.EvalStateDeriv(trans, ip, pointflux_mag); + pointflux_mag_bar += model_val_bar * dmodeldpointflux_mag; + + /// const double pointflux_mag = pointflux_norm / trans_weight; + double pointflux_norm_bar = 0.0; + double trans_weight_bar = 0.0; + pointflux_norm_bar += pointflux_mag_bar / trans_weight; + trans_weight_bar -= pointflux_mag_bar * pointflux_norm / pow(trans_weight, 2); + + /// const double pointflux_norm = pointflux.Norml2(); + pointflux_bar = 0.0; + add(pointflux_bar, pointflux_norm_bar / pointflux_norm, pointflux, pointflux_bar); + + /// const double curl_psi_dot_pointflux = curl_psi * pointflux; + curl_psi_bar = 0.0; + add(curl_psi_bar, curl_psi_dot_pointflux_bar, pointflux, curl_psi_bar); + add(pointflux_bar, curl_psi_dot_pointflux_bar, curl_psi, pointflux_bar); + + dshapedxt_bar = 0.0; + /// dshapedxt.MultTranspose(psi, curl_psi); + AddMultVWt(psi, curl_psi_bar, dshapedxt_bar); + /// dshapedxt.MultTranspose(elfun, pointflux); + AddMultVWt(elfun, pointflux_bar, dshapedxt_bar); + + /// Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + double adj_jac_bar_buffer[9] = {}; + DenseMatrix adj_jac_bar(adj_jac_bar_buffer, space_dim, space_dim); + MultAtB(dshape, dshapedxt_bar, adj_jac_bar); + + PointMat_bar = 0.0; + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); + + /// double w = alpha * ip.weight / trans_weight; + trans_weight_bar -= w_bar * alpha * ip.weight / pow(trans_weight, 2); + + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + mesh_coords_bar(k * mesh_ndof + j) += PointMat_bar(k, j); + } + } + } +} + void MagnetizationSource2DIntegrator::AssembleRHSElementVect( const mfem::FiniteElement &el, mfem::ElementTransformation &trans, @@ -3929,9 +4098,10 @@ void ForceIntegratorMeshSens3::AssembleRHSElementVect( Array vdofs; Vector vfun; Vector elfun; -#endif +#else auto &vdofs = force_integ.vdofs; auto &vfun = force_integ.vfun; +#endif /// get the proper element, transformation, and v vector const auto &v_el = *v.FESpace()->GetFE(element); diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index f0533f36..394d4510 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -85,6 +85,45 @@ class NonlinearDiffusionIntegrator : public mfem::NonlinearFormIntegrator #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix dshape, dshapedxt, point_flux_2_dot; mfem::Vector pointflux_norm_dot; +#endif + friend class NonlinearDiffusionIntegratorMeshRevSens; +}; + +/// Integrator to assemble d(psi^T R)/dX for the NonlinearDiffusionIntegrator +class NonlinearDiffusionIntegratorMeshRevSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] state - the state to use when evaluating d(psi^T R)/dX + /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX + /// \param[in] integ - reference to primal integrator + NonlinearDiffusionIntegratorMeshRevSens(mfem::GridFunction &state, + mfem::GridFunction &adjoint, + NonlinearDiffusionIntegrator &integ) + : state(state), adjoint(adjoint), integ(integ) + { } + + /// \brief - assemble an element's contribution to d(psi^T R)/dX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - d(psi^T R)/dX for the element + /// \note the LinearForm that assembles this integrator's FiniteElementSpace + /// MUST be the mesh's nodal finite element space + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// the state to use when evaluating d(psi^T R)/dX + mfem::GridFunction &state; + /// the adjoint to use when evaluating d(psi^T R)/dX + mfem::GridFunction &adjoint; + /// reference to primal integrator + NonlinearDiffusionIntegrator &integ; +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshapedxt_bar, PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun, psi; #endif }; diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 759e9db7..c5a3a548 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -11,7 +11,6 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") using namespace mfem; using namespace electromag_data; - const int dim = 2; // templating is hard here because mesh constructors double delta = 1e-5; // generate a 6 element mesh @@ -20,7 +19,10 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") num_edge, Element::TRIANGLE); mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + NonLinearCoefficient nu; + // LinearCoefficient nu; for (int p = 1; p <= 4; ++p) { DYNAMIC_SECTION( "...for degree p = " << p ) @@ -29,11 +31,10 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") FiniteElementSpace fes(&mesh, &fec); // initialize state; here we randomly perturb a constant state - GridFunction a(&fes); + GridFunction state(&fes); FunctionCoefficient pert(randState); - a.ProjectCoefficient(pert); + state.ProjectCoefficient(pert); - NonLinearCoefficient nu; NonlinearForm res(&fes); res.AddDomainIntegrator(new mach::NonlinearDiffusionIntegrator(nu)); @@ -42,16 +43,16 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") v.ProjectCoefficient(pert); // evaluate the Jacobian and compute its product with v - Operator& jac = res.GetGradient(a); + Operator& jac = res.GetGradient(state); GridFunction jac_v(&fes); jac.Mult(v, jac_v); // now compute the finite-difference approximation... GridFunction r(&fes), jac_v_fd(&fes); - a.Add(-delta, v); - res.Mult(a, r); - a.Add(2*delta, v); - res.Mult(a, jac_v_fd); + state.Add(-delta, v); + res.Mult(state, r); + state.Add(2*delta, v); + res.Mult(state, jac_v_fd); jac_v_fd -= r; jac_v_fd /= (2*delta); @@ -63,6 +64,80 @@ TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") } } +TEST_CASE("NonlinearDiffusionIntegratorMeshRevSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state; here we randomly perturb a constant state + GridFunction state(&fes); + GridFunction adjoint(&fes); + FunctionCoefficient pert(randState); + state.ProjectCoefficient(pert); + adjoint.ProjectCoefficient(pert); + + NonlinearForm res(&fes); + auto *integ = new mach::NonlinearDiffusionIntegrator(nu); + res.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // initialize the vector that we use to perturb the mesh nodes + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // evaluate d(psi^T R)/dx and contract with v + LinearForm dfdx(&mesh_fes); + dfdx.AddDomainIntegrator( + new mach::NonlinearDiffusionIntegratorMeshRevSens(state, adjoint, *integ)); + dfdx.Assemble(); + double dfdx_v = dfdx * v; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + GridFunction r(&fes); + x_pert.Add(delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Mult(state, r); + double dfdx_v_fd = adjoint * r; + x_pert.Add(-2 * delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Mult(state, r); + dfdx_v_fd -= adjoint * r; + dfdx_v_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + + TEST_CASE("CurlCurlNLFIntegrator::AssembleElementGrad - linear", "[CurlCurlNLFIntegrator]") { From 6351b0afe20b46cff96638b69eb9545d5b76a853 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Wed, 31 Aug 2022 13:41:30 -0400 Subject: [PATCH 42/72] make format --- src/physics/electromagnetics/electromag_integ.cpp | 11 ++++++++--- src/physics/electromagnetics/electromag_integ.hpp | 7 ++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 7ec5834c..f42b6c10 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -377,18 +377,23 @@ void NonlinearDiffusionIntegratorMeshRevSens::AssembleRHSElementVect( /// double model_val = model.Eval(trans, ip, pointflux_mag); double pointflux_mag_bar = 0.0; - const double dmodeldpointflux_mag = model.EvalStateDeriv(trans, ip, pointflux_mag); + const double dmodeldpointflux_mag = + model.EvalStateDeriv(trans, ip, pointflux_mag); pointflux_mag_bar += model_val_bar * dmodeldpointflux_mag; /// const double pointflux_mag = pointflux_norm / trans_weight; double pointflux_norm_bar = 0.0; double trans_weight_bar = 0.0; pointflux_norm_bar += pointflux_mag_bar / trans_weight; - trans_weight_bar -= pointflux_mag_bar * pointflux_norm / pow(trans_weight, 2); + trans_weight_bar -= + pointflux_mag_bar * pointflux_norm / pow(trans_weight, 2); /// const double pointflux_norm = pointflux.Norml2(); pointflux_bar = 0.0; - add(pointflux_bar, pointflux_norm_bar / pointflux_norm, pointflux, pointflux_bar); + add(pointflux_bar, + pointflux_norm_bar / pointflux_norm, + pointflux, + pointflux_bar); /// const double curl_psi_dot_pointflux = curl_psi * pointflux; curl_psi_bar = 0.0; diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 394d4510..09d09451 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -90,15 +90,16 @@ class NonlinearDiffusionIntegrator : public mfem::NonlinearFormIntegrator }; /// Integrator to assemble d(psi^T R)/dX for the NonlinearDiffusionIntegrator -class NonlinearDiffusionIntegratorMeshRevSens : public mfem::LinearFormIntegrator +class NonlinearDiffusionIntegratorMeshRevSens + : public mfem::LinearFormIntegrator { public: /// \param[in] state - the state to use when evaluating d(psi^T R)/dX /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX /// \param[in] integ - reference to primal integrator NonlinearDiffusionIntegratorMeshRevSens(mfem::GridFunction &state, - mfem::GridFunction &adjoint, - NonlinearDiffusionIntegrator &integ) + mfem::GridFunction &adjoint, + NonlinearDiffusionIntegrator &integ) : state(state), adjoint(adjoint), integ(integ) { } From c79b9a42fab107b01627f30ff3343c15125bb90f Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 6 Sep 2022 22:48:20 -0400 Subject: [PATCH 43/72] added reverse mode mesh sensitivity integrator for 2D magnetic source linear form integrator and tested it --- .../electromagnetics/electromag_integ.cpp | 162 ++++++++++++++++++ .../electromagnetics/electromag_integ.hpp | 39 +++++ test/unit/test_electromag_integ.cpp | 86 ++++++++++ 3 files changed, 287 insertions(+) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index f42b6c10..e9d33f5d 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -511,6 +511,168 @@ void MagnetizationSource2DIntegrator::AssembleRHSElementVect( } } +void MagnetizationSource2DIntegratorMeshRevSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *adjoint.FESpace()->GetFE(element); + auto &trans = *adjoint.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + /// get the proper element, transformation, and state vector +#ifdef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector psi; +#endif + auto *dof_tr = adjoint.FESpace()->GetElementVDofs(element, vdofs); + adjoint.GetSubVector(vdofs, psi); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(psi); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape; + DenseMatrix dshapedxt; + DenseMatrix dshapedxt_bar; + DenseMatrix PointMat_bar; + Vector scratch_bar; +#else + auto &dshape = integ.dshape; + auto &dshapedxt = integ.dshapedxt; +#endif + + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + + dshapedxt_bar.SetSize(ndof, space_dim); + scratch_bar.SetSize(ndof); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + double mag_flux_buffer[3] = {}; + Vector mag_flux(mag_flux_buffer, space_dim); + double mag_flux_bar_buffer[3] = {}; + Vector mag_flux_bar(mag_flux_bar_buffer, space_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(mesh_trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + if (el.Space() == FunctionSpace::rQk) + { + ir = &RefinedIntRules.Get(el.GetGeomType(), order); + } + else + { + ir = &IntRules.Get(el.GetGeomType(), order); + } + } + + auto &alpha = integ.alpha; + auto &M = integ.M; + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double w = alpha * ip.weight; + + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + + M.Eval(mag_flux, trans, ip); + // mag_flux *= w; + + Vector grad_column_0; + dshapedxt.GetColumnReference(0, grad_column_0); + + Vector grad_column_1; + dshapedxt.GetColumnReference(1, grad_column_1); + + // scratch = 0.0; + // add(mag_flux(1), grad_column_0, -mag_flux(0), grad_column_1, scratch); + + // const double psi_dot_scratch = psi * scratch; + + // elvect += scratch; + /// dummy functional for adjoint-weighted residual + // fun += psi_dot_scratch * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun += psi_dot_scratch * w; + double psi_dot_scratch_bar = fun_bar * w; + // double w_bar = fun_bar * psi_dot_scratch; + + /// const double psi_dot_scratch = psi * scratch; + scratch_bar = 0.0; + scratch_bar.Add(psi_dot_scratch_bar, psi); + + + /// add(mag_flux(1), grad_column_0, -mag_flux(0), grad_column_1, scratch); + /// Vector grad_column_1; + /// dshapedxt.GetColumnReference(1, grad_column_1); + /// Vector grad_column_0; + /// dshapedxt.GetColumnReference(0, grad_column_0); + dshapedxt_bar = 0.0; + Vector grad_bar_column_1; + dshapedxt_bar.GetColumnReference(1, grad_bar_column_1); + Vector grad_bar_column_0; + dshapedxt_bar.GetColumnReference(0, grad_bar_column_0); + + mag_flux_bar(1) = grad_column_0 * scratch_bar; + mag_flux_bar(0) = -(grad_column_1 * scratch_bar); + + grad_bar_column_0.Add(mag_flux(1), scratch_bar); + grad_bar_column_1.Add(-mag_flux(0), scratch_bar); + + /// M.Eval(mag_flux, trans, ip); + PointMat_bar = 0.0; + M.EvalRevDiff(mag_flux_bar, trans, ip, PointMat_bar); + + /// Mult(dshape, trans.AdjugateJacobian(), dshapedxt); + double adj_jac_bar_buffer[9] = {}; + DenseMatrix adj_jac_bar(adj_jac_bar_buffer, space_dim, space_dim); + MultAtB(dshape, dshapedxt_bar, adj_jac_bar); + + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + mesh_coords_bar(k * mesh_ndof + j) += PointMat_bar(k, j); + } + } + } +} + void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun, diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 09d09451..d2495501 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -149,6 +149,45 @@ class MagnetizationSource2DIntegrator : public mfem::LinearFormIntegrator #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix dshape, dshapedxt; mfem::Vector scratch; +#endif + friend class MagnetizationSource2DIntegratorMeshRevSens; +}; + +/// Integrator to assemble d(psi^T R)/dX for the MagnetizationSource2DIntegrator +class MagnetizationSource2DIntegratorMeshRevSens + : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] state - the state to use when evaluating d(psi^T R)/dX + /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX + /// \param[in] integ - reference to primal integrator + MagnetizationSource2DIntegratorMeshRevSens( + mfem::GridFunction &adjoint, + MagnetizationSource2DIntegrator &integ) + : adjoint(adjoint), integ(integ) + { } + + /// \brief - assemble an element's contribution to d(psi^T R)/dX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - d(psi^T R)/dX for the element + /// \note the LinearForm that assembles this integrator's FiniteElementSpace + /// MUST be the mesh's nodal finite element space + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// the adjoint to use when evaluating d(psi^T R)/dX + mfem::GridFunction &adjoint; + /// reference to primal integrator + MagnetizationSource2DIntegrator &integ; +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix dshapedxt_bar, PointMat_bar; + mfem::Vector scratch_bar; + mfem::Array vdofs; + mfem::Vector psi; #endif }; diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index c5a3a548..b11a5c40 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -137,6 +137,92 @@ TEST_CASE("NonlinearDiffusionIntegratorMeshRevSens::AssembleRHSElementVect") } } +TEST_CASE("MagnetizationSource2DIntegratorMeshRevSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::VectorFunctionCoefficient model(dim, + [](const Vector &x, Vector &m) + { + for (int i = 0; i < x.Size(); ++i) + { + m(i) = pow(x(i), 2); + } + }, + [](const Vector &m_bar, const Vector &x, Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) = 2 * x(i) * m_bar(i); + } + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize adjoint; here we randomly perturb a constant state + GridFunction adjoint(&fes); + FunctionCoefficient pert(randState); + adjoint.ProjectCoefficient(pert); + + LinearForm res(&fes); + auto *integ = new mach::MagnetizationSource2DIntegrator(model); + res.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // initialize the vector that we use to perturb the mesh nodes + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // evaluate d(psi^T R)/dx and contract with v + LinearForm dfdx(&mesh_fes); + dfdx.AddDomainIntegrator( + new mach::MagnetizationSource2DIntegratorMeshRevSens(adjoint, *integ)); + dfdx.Assemble(); + double dfdx_v = dfdx * v; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + GridFunction r(&fes); + x_pert.Add(delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Update(); + res.Assemble(); + double dfdx_v_fd = adjoint * res; + x_pert.Add(-2 * delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Update(); + res.Assemble(); + dfdx_v_fd -= adjoint * res; + dfdx_v_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} TEST_CASE("CurlCurlNLFIntegrator::AssembleElementGrad - linear", "[CurlCurlNLFIntegrator]") From a32359d2b05213b12963a5f86166270c219237ac Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 6 Sep 2022 22:48:43 -0400 Subject: [PATCH 44/72] make format --- src/physics/electromagnetics/electromag_integ.cpp | 1 - src/physics/electromagnetics/electromag_integ.hpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index e9d33f5d..2f894623 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -633,7 +633,6 @@ void MagnetizationSource2DIntegratorMeshRevSens::AssembleRHSElementVect( scratch_bar = 0.0; scratch_bar.Add(psi_dot_scratch_bar, psi); - /// add(mag_flux(1), grad_column_0, -mag_flux(0), grad_column_1, scratch); /// Vector grad_column_1; /// dshapedxt.GetColumnReference(1, grad_column_1); diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index d2495501..e37a70e3 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -162,8 +162,8 @@ class MagnetizationSource2DIntegratorMeshRevSens /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX /// \param[in] integ - reference to primal integrator MagnetizationSource2DIntegratorMeshRevSens( - mfem::GridFunction &adjoint, - MagnetizationSource2DIntegrator &integ) + mfem::GridFunction &adjoint, + MagnetizationSource2DIntegrator &integ) : adjoint(adjoint), integ(integ) { } From 21198bbe9a572fa3a840931b0cc0398c215be9af Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 13 Sep 2022 16:45:05 -0400 Subject: [PATCH 45/72] differentiating various forgotten outputs, working on getting all MotorModel outputs differentiated and verified --- mach/pyMach/machSolver.cpp | 39 +- mach/test/data/simple_square.mesh | 58 ++ mach/test/test_mach_functional.py | 102 ++- src/physics/common_outputs.cpp | 41 +- src/physics/common_outputs.hpp | 17 +- .../electromagnetics/electromag_integ.cpp | 8 +- .../electromagnetics/electromag_integ.hpp | 13 + src/physics/mfem_common_integ.cpp | 639 +++++++++++++++++- src/physics/mfem_common_integ.hpp | 130 +++- test/unit/test_mfem_common_integ.cpp | 272 ++++++++ 10 files changed, 1241 insertions(+), 78 deletions(-) create mode 100644 mach/test/data/simple_square.mesh diff --git a/mach/pyMach/machSolver.cpp b/mach/pyMach/machSolver.cpp index e7b731ef..5a675746 100644 --- a/mach/pyMach/machSolver.cpp +++ b/mach/pyMach/machSolver.cpp @@ -43,28 +43,37 @@ void initSolver(py::module &m) // py::arg("json_options"), // py::arg("comm") = mpi_comm(MPI_COMM_WORLD)) .def("getOptions", &AbstractSolver2::getOptions) + // .def( + // "setState", + // [](AbstractSolver2 &self, + // const std::function &fun, + // const py::array_t &state, + // const std::string &name) + // { + // auto state_vec = npBufferToMFEMVector(state); + // self.setState(fun, state_vec, name); + // }, + // py::arg("fun"), + // py::arg("state"), + // py::arg("name") = "state") .def( "setState", [](AbstractSolver2 &self, - const std::function &fun, + // const std::function &fun, + const std::function &)> &fun, const py::array_t &state, const std::string &name) { + auto cpp_fun = [&fun](const mfem::Vector &x){ + py::array_t py_x{ + x.Size(), /* Buffer dimensions */ + x.GetData() /* Pointer to buffer */ + }; + return fun(py_x); + }; auto state_vec = npBufferToMFEMVector(state); - self.setState(fun, state_vec, name); - }, - py::arg("fun"), - py::arg("state"), - py::arg("name") = "state") - .def( - "setState", - [](AbstractSolver2 &self, - const std::function &fun, - const py::array_t &state, - const std::string &name) - { - auto state_vec = npBufferToMFEMVector(state); - self.setState(fun, state_vec, name); + // self.setState(fun, state_vec, name); + self.setState(cpp_fun, state_vec, name); }, py::arg("fun"), py::arg("state"), diff --git a/mach/test/data/simple_square.mesh b/mach/test/data/simple_square.mesh new file mode 100644 index 00000000..c806eeeb --- /dev/null +++ b/mach/test/data/simple_square.mesh @@ -0,0 +1,58 @@ +MFEM mesh v1.0 + +# +# MFEM Geometry Types (see mesh/geom.hpp): +# +# POINT = 0 +# SEGMENT = 1 +# TRIANGLE = 2 +# SQUARE = 3 +# TETRAHEDRON = 4 +# CUBE = 5 +# PRISM = 6 +# PYRAMID = 7 +# + +dimension +2 + +elements +8 +1 2 0 4 3 +1 2 4 0 1 +1 2 1 5 4 +1 2 5 1 2 +1 2 3 7 6 +1 2 7 3 4 +1 2 4 8 7 +1 2 8 4 5 + +boundary +8 +1 1 0 1 +1 1 1 2 +3 1 7 6 +3 1 8 7 +4 1 3 0 +4 1 6 3 +2 1 2 5 +2 1 5 8 + +vertices +9 + +nodes +FiniteElementSpace +FiniteElementCollection: H1_2D_P1 +VDim: 2 +Ordering: 1 + +0 0 +0.5 0 +1 0 +0 0.5 +0.5 0.5 +1 0.5 +0 1 +0.5 1 +1 1 diff --git a/mach/test/test_mach_functional.py b/mach/test/test_mach_functional.py index 85da6f76..8f04bf68 100644 --- a/mach/test/test_mach_functional.py +++ b/mach/test/test_mach_functional.py @@ -33,7 +33,6 @@ class TestEMFunctionals(unittest.TestCase): "ring": { "material": "copperwire", "attrs": [1, 2], - "linear": True } }, "bcs": { @@ -71,9 +70,53 @@ class TestEMFunctionals(unittest.TestCase): }, "components": { "ring": { - "material": "box1", "attrs": [1], - "linear": True + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + }, + } + }, + "bcs": { + "essential": "all" + }, + "current": { + "test": { + "z": [1] + } + } + } + + square_options = { + "mesh": { + "file": "data/simple_square.mesh", + "refine": 0 + }, + "space-dis": { + "basis-type": "h1", + "degree": 1 + }, + "lin-solver": { + "type": "pcg", + "printlevel": 1, + "maxiter": 100, + "abstol": 1e-14, + "reltol": 1e-14 + }, + "nonlin-solver": { + "type": "newton", + "printlevel": 1, + "maxiter": 5, + "reltol": 1e-6, + "abstol": 1e-6 + }, + "components": { + "ring": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + }, } }, "bcs": { @@ -257,12 +300,18 @@ def test_flux_density(self): prob = om.Problem() emSolver = PDESolver(type="magnetostatic", - solver_options=self.box_options, + solver_options=self.square_options, comm=prob.comm) state_size = emSolver.getFieldSize("state") state = np.random.randn(state_size) + def field_func(x): + return x[0]**2 + x[1]**2 + emSolver.setState(field_func, state) + + print(f"state: {state}") + ivc = prob.model.add_subsystem("ivc", om.IndepVarComp(), promotes_outputs=["state"]) @@ -272,19 +321,48 @@ def test_flux_density(self): MachMesh(solver=emSolver), promotes_outputs=["*"]) - flux = prob.model.add_subsystem("flux_density", - MachFunctional(solver=emSolver, - func="flux_density", - check_partials=True, - depends=["state", "mesh_coords"]), - promotes_inputs=[("mesh_coords", "x_em0"), "state"], - promotes_outputs=["flux_density"]) - flux.set_check_partial_options(wrt="*", directional=True) + # flux = prob.model.add_subsystem("flux_density", + # MachFunctional(solver=emSolver, + # func="flux_density", + # check_partials=True, + # depends=["state", "mesh_coords"]), + # promotes_inputs=[("mesh_coords", "x_em0"), "state"], + # promotes_outputs=["flux_density"]) + # flux.set_check_partial_options(wrt="*", directional=True) + + flux_mag = prob.model.add_subsystem("flux_magnitude", + MachFunctional(solver=emSolver, + func="flux_magnitude", + check_partials=True, + depends=["state", "mesh_coords"]), + promotes_inputs=[("mesh_coords", "x_em0"), "state"], + promotes_outputs=["flux_magnitude"]) + flux_mag.set_check_partial_options(wrt="*", directional=True) + + # avg_flux = prob.model.add_subsystem("average_flux_magnitude", + # MachFunctional(solver=emSolver, + # func="average_flux_magnitude", + # check_partials=True, + # depends=["state", "mesh_coords"]), + # promotes_inputs=[("mesh_coords", "x_em0"), "state"], + # promotes_outputs=["average_flux_magnitude"]) + # avg_flux.set_check_partial_options(wrt="*", directional=True) + + max_flux = prob.model.add_subsystem("max_flux_magnitude", + MachFunctional(solver=emSolver, + func="max_flux_magnitude", + func_options={"rho": 1}, + check_partials=True, + depends=["state", "mesh_coords"]), + promotes_inputs=[("mesh_coords", "x_em0"), "state"], + promotes_outputs=["max_flux_magnitude"]) + max_flux.set_check_partial_options(wrt="*", directional=True) prob.setup() prob.run_model() data = prob.check_partials(form="central") assert_check_partials(data) + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index 668a837d..664205e1 100644 --- a/src/physics/common_outputs.cpp +++ b/src/physics/common_outputs.cpp @@ -1,6 +1,7 @@ #include #include +#include "mach_load.hpp" #include "mfem.hpp" #include "coefficient.hpp" @@ -84,12 +85,6 @@ StateAverageFunctional::StateAverageFunctional( } } -AverageMagnitudeCurlState::AverageMagnitudeCurlState( - mfem::ParFiniteElementSpace &fes, - std::map &fields) - : AverageMagnitudeCurlState(fes, fields, {}) -{ } - AverageMagnitudeCurlState::AverageMagnitudeCurlState( mfem::ParFiniteElementSpace &fes, std::map &fields, @@ -135,6 +130,40 @@ IEAggregateFunctional::IEAggregateFunctional( } } +double jacobianVectorProduct(IECurlMagnitudeAggregateFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) +{ + const MachInputs &inputs = *output.inputs; + double num = calcOutput(output.numerator, inputs); + double denom = calcOutput(output.denominator, inputs); + + auto out_dot = denom * jacobianVectorProduct(output.numerator, wrt_dot, wrt); + out_dot -= num * jacobianVectorProduct(output.denominator, wrt_dot, wrt); + out_dot /= pow(denom, 2); + return out_dot; +} + +void vectorJacobianProduct(IECurlMagnitudeAggregateFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + const MachInputs &inputs = *output.inputs; + double num = calcOutput(output.numerator, inputs); + double denom = calcOutput(output.denominator, inputs); + + output.scratch.SetSize(wrt_bar.Size()); + + output.scratch = 0.0; + vectorJacobianProduct(output.numerator, out_bar, wrt, output.scratch); + wrt_bar.Add(1 / denom, output.scratch); + + output.scratch = 0.0; + vectorJacobianProduct(output.denominator, out_bar, wrt, output.scratch); + wrt_bar.Add(-num / pow(denom, 2), output.scratch); +} + IECurlMagnitudeAggregateFunctional::IECurlMagnitudeAggregateFunctional( mfem::ParFiniteElementSpace &fes, std::map &fields, diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index 9c588e18..e805b7da 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -146,7 +146,9 @@ class AverageMagnitudeCurlState } AverageMagnitudeCurlState(mfem::ParFiniteElementSpace &fes, - std::map &fields); + std::map &fields) + : AverageMagnitudeCurlState(fes, fields, {}) + { } AverageMagnitudeCurlState(mfem::ParFiniteElementSpace &fes, std::map &fields, @@ -214,6 +216,7 @@ class IECurlMagnitudeAggregateFunctional friend void setInputs(IECurlMagnitudeAggregateFunctional &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.numerator, inputs); setInputs(output.denominator, inputs); } @@ -226,6 +229,16 @@ class IECurlMagnitudeAggregateFunctional return num / denom; } + friend double jacobianVectorProduct( + IECurlMagnitudeAggregateFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); + + friend void vectorJacobianProduct(IECurlMagnitudeAggregateFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); + IECurlMagnitudeAggregateFunctional( mfem::ParFiniteElementSpace &fes, std::map &fields, @@ -234,6 +247,8 @@ class IECurlMagnitudeAggregateFunctional private: FunctionalOutput numerator; FunctionalOutput denominator; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; } // namespace mach diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 2f894623..ca4ad96f 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -14,7 +14,7 @@ double calcMagneticEnergy(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -35,7 +35,7 @@ double calcMagneticEnergyDot(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -64,7 +64,7 @@ double calcMagneticEnergyDoubleDot(ElementTransformation &trans, { /// TODO: use a composite rule instead or find a way to just directly /// integrate B-H curve - const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 10); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double d2endB2 = 0.0; @@ -1961,7 +1961,7 @@ void MagneticEnergyIntegrator::AssembleElementVector( /// const double b_vec_norm = b_vec.Norml2(); add(b_vec_bar, b_vec_norm_bar / b_vec_norm, b_vec, b_vec_bar); - // curlshape_dFt.AddMultTranspose(elfun, b_vec); + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); curlshape_dFt.AddMult(b_vec_bar, elfun_bar); } } diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index e37a70e3..ce85ef0c 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1322,6 +1322,19 @@ class ForceIntegratorMeshSens3 : public mfem::LinearFormIntegrator #endif }; +inline void addSensitivityIntegrator( + ForceIntegrator3 &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator(new ForceIntegratorMeshSens3( + fields.at("state").gridFunc(), primal_integ)); +} + /// Functional integrator to compute forces/torques based on the virtual work /// method class ForceIntegrator : public mfem::NonlinearFormIntegrator diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index 1b394dec..435fffc7 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -54,18 +54,18 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( { /// number of degrees of freedom int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE mfem::DenseMatrix curlshape; mfem::DenseMatrix curlshape_dFt; #endif - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); double b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, dimc); + Vector b_vec(b_vec_buffer, curl_dim); const auto *ir = IntRule; if (ir == nullptr) @@ -95,14 +95,15 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( double w = ip.weight * trans.Weight(); - if (dim == 3) + if (space_dim == 3) { el.CalcCurlShape(ip, curlshape); MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } curlshape_dFt.AddMultTranspose(elfun, b_vec); const double b_vec_norm = b_vec.Norml2(); @@ -194,21 +195,36 @@ double IECurlMagnitudeAggregateIntegratorNumerator::GetElementEnergy( const mfem::Vector &elfun) { int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - mfem::DenseMatrix curlshape(ndof, dimc); - mfem::DenseMatrix curlshape_dFt(ndof, dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + mfem::DenseMatrix curlshape(ndof, curl_dim); + mfem::DenseMatrix curlshape_dFt(ndof, curl_dim); #endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); double curl_vec_buffer[3]; - Vector curl_vec(curl_vec_buffer, dimc); + Vector curl_vec(curl_vec_buffer, curl_dim); - const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } double fun = 0.0; for (int i = 0; i < ir->GetNPoints(); ++i) @@ -218,24 +234,293 @@ double IECurlMagnitudeAggregateIntegratorNumerator::GetElementEnergy( const auto &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - if (dim == 3) + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) { el.CalcCurlShape(ip, curlshape); MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } curlshape_dFt.AddMultTranspose(elfun, curl_vec); const double curl_vec_norm = curl_vec.Norml2(); - const double curl_mag = curl_vec_norm / trans.Weight(); + const double curl_mag = curl_vec_norm / trans_weight; - fun += ip.weight * trans.Weight() * curl_mag * exp(rho * curl_mag); + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + fun += curl_mag * exp_rho_curl_mag * w; } return fun; } +void IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) +{ + int ndof = el.GetDof(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + +#ifdef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape(ndof, curl_dim); + mfem::DenseMatrix curlshape_dFt(ndof, curl_dim); +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + curl_vec = 0.0; + + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + curlshape_dFt.AddMultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; + + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + // fun += curl_mag * exp_rho_curl_mag * w; + + /// Start reverse pass... + /// fun += curl_mag * exp_rho_curl_mag * w; + double fun_bar = 1.0; + + double curl_mag_bar = 0.0; + double exp_rho_curl_mag_bar = 0.0; + // double w_bar = 0.0; + curl_mag_bar += fun_bar * exp_rho_curl_mag * w; + exp_rho_curl_mag_bar += fun_bar * curl_mag * w; + // w_bar += fun_bar * curl_mag * exp_rho_curl_mag; + + /// const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + curl_mag_bar += + exp_rho_curl_mag_bar * rho / actual_max * exp_rho_curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + // double trans_weight_bar = -curl_mag_bar * curl_vec_norm / + // pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt.AddMult(curl_vec_bar, elfun_bar); + } +} + +void IECurlMagnitudeAggregateIntegratorNumeratorMeshSens:: + AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ +const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#else + auto &curlshape = integ.curlshape; + auto &curlshape_dFt = integ.curlshape_dFt; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + // curlshape_dFt_bar.SetSize(curl_dim, ndof); + PointMat_bar.SetSize(curl_dim, mesh_ndof); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto rho = integ.rho; + auto actual_max = integ.actual_max; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; + + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + + // fun += curl_mag * exp_rho_curl_mag * w; + + /// Start reverse pass... + /// fun += exp_rho_curl_mag * w; + double fun_bar = 1.0; + + double curl_mag_bar = 0.0; + double exp_rho_curl_mag_bar = 0.0; + double w_bar = 0.0; + curl_mag_bar += fun_bar * exp_rho_curl_mag * w; + exp_rho_curl_mag_bar += fun_bar * curl_mag * w; + w_bar += fun_bar * curl_mag * exp_rho_curl_mag; + + /// const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + curl_mag_bar += + exp_rho_curl_mag_bar * rho / actual_max * exp_rho_curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + double trans_weight_bar = + -curl_mag_bar * curl_vec_norm / pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); + MultVWt(curl_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt_bar.SetSize(ndof, curl_dim); + MultVWt(elfun, curl_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9] = {}; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + // double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setOptions(IECurlMagnitudeAggregateIntegratorDenominator &integ, const nlohmann::json &options) { @@ -251,48 +536,328 @@ double IECurlMagnitudeAggregateIntegratorDenominator::GetElementEnergy( const mfem::Vector &elfun) { int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; #ifdef MFEM_THREAD_SAFE - mfem::DenseMatrix curlshape(ndof, dimc); - mfem::DenseMatrix curlshape_dFt(ndof, dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + mfem::DenseMatrix curlshape(ndof, curl_dim); + mfem::DenseMatrix curlshape_dFt(ndof, curl_dim); #endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); - double curl_vec_buffer[3]; - Vector curl_vec(curl_vec_buffer, dimc); + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); - const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } double fun = 0.0; for (int i = 0; i < ir->GetNPoints(); ++i) { - curl_vec = 0.0; - const auto &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - if (dim == 3) + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) { el.CalcCurlShape(ip, curlshape); MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - el.CalcCurlShape(ip, curlshape_dFt); + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } - curlshape_dFt.AddMultTranspose(elfun, curl_vec); + + curlshape_dFt.MultTranspose(elfun, curl_vec); const double curl_vec_norm = curl_vec.Norml2(); - const double curl_mag = curl_vec_norm / trans.Weight(); + const double curl_mag = curl_vec_norm / trans_weight; + + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); - fun += ip.weight * trans.Weight() * exp(rho * curl_mag); + fun += exp_rho_curl_mag * w; } return fun; } +void IECurlMagnitudeAggregateIntegratorDenominator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) +{ + int ndof = el.GetDof(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + +#ifdef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape(ndof, curl_dim); + mfem::DenseMatrix curlshape_dFt(ndof, curl_dim); +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; + + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + // fun += exp_rho_curl_mag * w; + + /// Start reverse pass... + /// fun += exp_rho_curl_mag * w; + double fun_bar = 1.0; + + double exp_rho_curl_mag_bar = 0.0; + // double w_bar = 0.0; + exp_rho_curl_mag_bar += fun_bar * w; + // w_bar += fun_bar * exp_rho_curl_mag; + + /// const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + double curl_mag_bar = 0.0; + curl_mag_bar += + exp_rho_curl_mag_bar * rho / actual_max * exp_rho_curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + // double trans_weight_bar = -curl_mag_bar * curl_vec_norm / + // pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt.AddMult(curl_vec_bar, elfun_bar); + } +} + +void IECurlMagnitudeAggregateIntegratorDenominatorMeshSens:: + AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#else + auto &curlshape = integ.curlshape; + auto &curlshape_dFt = integ.curlshape_dFt; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + // curlshape_dFt_bar.SetSize(curl_dim, ndof); + PointMat_bar.SetSize(curl_dim, mesh_ndof); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto rho = integ.rho; + auto actual_max = integ.actual_max; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; + + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + + // fun += exp_rho_curl_mag * w; + + /// Start reverse pass... + /// fun += exp_rho_curl_mag * w; + double fun_bar = 1.0; + + double exp_rho_curl_mag_bar = 0.0; + double w_bar = 0.0; + exp_rho_curl_mag_bar += fun_bar * w; + w_bar += fun_bar * exp_rho_curl_mag; + + /// const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + double curl_mag_bar = 0.0; + curl_mag_bar += + exp_rho_curl_mag_bar * rho / actual_max * exp_rho_curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + double trans_weight_bar = + -curl_mag_bar * curl_vec_norm / pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); + MultVWt(curl_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt_bar.SetSize(ndof, curl_dim); + MultVWt(elfun, curl_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9] = {}; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + // double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void DiffusionIntegratorMeshSens::AssembleRHSElementVect( const mfem::FiniteElement &mesh_el, mfem::ElementTransformation &mesh_trans, diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 7d212437..15949d4b 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -1,6 +1,7 @@ #ifndef MACH_MFEM_COMMON_INTEG #define MACH_MFEM_COMMON_INTEG +#include #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -97,20 +98,82 @@ class IECurlMagnitudeAggregateIntegratorNumerator friend void setOptions(IECurlMagnitudeAggregateIntegratorNumerator &integ, const nlohmann::json &options); - IECurlMagnitudeAggregateIntegratorNumerator(const double rho) : rho(rho) { } + IECurlMagnitudeAggregateIntegratorNumerator(double rho, + double actual_max = 1.0) + : rho(rho), actual_max(actual_max) + { } double GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + private: /// aggregation parameter rho double rho; + /// actual maximum from the data, makes the calculation more stable + double actual_max; #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix curlshape, curlshape_dFt; +#endif + friend class IECurlMagnitudeAggregateIntegratorNumeratorMeshSens; +}; + +class IECurlMagnitudeAggregateIntegratorNumeratorMeshSens + : public mfem::LinearFormIntegrator +{ +public: + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] state - the state vector to evaluate force at + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrators + IECurlMagnitudeAggregateIntegratorNumeratorMeshSens( + mfem::GridFunction &state, + IECurlMagnitudeAggregateIntegratorNumerator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating force + mfem::GridFunction &state; + /// reference to primal integrator + IECurlMagnitudeAggregateIntegratorNumerator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape_dFt_bar; + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun; #endif }; +inline void addSensitivityIntegrator( + IECurlMagnitudeAggregateIntegratorNumerator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorNumeratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); +} + class IECurlMagnitudeAggregateIntegratorDenominator : public mfem::NonlinearFormIntegrator { @@ -118,21 +181,82 @@ class IECurlMagnitudeAggregateIntegratorDenominator friend void setOptions(IECurlMagnitudeAggregateIntegratorDenominator &integ, const nlohmann::json &options); - IECurlMagnitudeAggregateIntegratorDenominator(const double rho) - : rho(rho) { } + IECurlMagnitudeAggregateIntegratorDenominator(const double rho, + double actual_max = 1.0) + : rho(rho), actual_max(actual_max) + { } double GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + private: /// aggregation parameter rho double rho; + /// actual maximum from the data, makes the calculation more stable + double actual_max; #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix curlshape, curlshape_dFt; +#endif + friend class IECurlMagnitudeAggregateIntegratorDenominatorMeshSens; +}; + +class IECurlMagnitudeAggregateIntegratorDenominatorMeshSens + : public mfem::LinearFormIntegrator +{ +public: + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] state - the state vector to evaluate force at + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrators + IECurlMagnitudeAggregateIntegratorDenominatorMeshSens( + mfem::GridFunction &state, + IECurlMagnitudeAggregateIntegratorDenominator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating force + mfem::GridFunction &state; + /// reference to primal integrator + IECurlMagnitudeAggregateIntegratorDenominator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape_dFt_bar; + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun; #endif }; +inline void addSensitivityIntegrator( + IECurlMagnitudeAggregateIntegratorDenominator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorDenominatorMeshSens( + fields.at("state").gridFunc(), primal_integ)); +} + class DiffusionIntegratorMeshSens final : public mfem::LinearFormIntegrator { public: diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index a14b2758..ddb534b7 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -114,6 +114,278 @@ TEST_CASE("StateIntegrator::GetElementEnergy (3D)") REQUIRE(rms == Approx(sqrt(2)/2).margin(1e-10)); } +TEST_CASE("IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::IECurlMagnitudeAggregateIntegratorNumerator(1.0, actual_max)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + FunctionCoefficient pert(randState); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("IECurlMagnitudeAggregateIntegratorNumeratorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + // FunctionCoefficient pert(randState); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + auto *integ = new mach::IECurlMagnitudeAggregateIntegratorNumerator(1.0, actual_max); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::IECurlMagnitudeAggregateIntegratorNumeratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + +TEST_CASE("IECurlMagnitudeAggregateIntegratorDenominator::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + // LinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::IECurlMagnitudeAggregateIntegratorDenominator(1.0, actual_max)); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + FunctionCoefficient pert(randState); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("IECurlMagnitudeAggregateIntegratorDenominatorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + // FunctionCoefficient pert(randState); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + auto *integ = new mach::IECurlMagnitudeAggregateIntegratorDenominator(1.0, actual_max); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::IECurlMagnitudeAggregateIntegratorDenominatorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("DiffusionIntegratorMeshSens::AssembleRHSElementVect") { using namespace mfem; From 604a8837460f495dfd826776654d30535a198bb7 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 13 Sep 2022 20:43:41 -0400 Subject: [PATCH 46/72] finished differentiating 2D flux density transfer, average flux densitiy calc, 2D flux density magnitude calc, and max flux magnitude calcs --- mach/pyMach/machSolver.cpp | 41 +-- mach/test/test_mach_functional.py | 41 +-- src/physics/common_outputs.cpp | 35 +++ src/physics/common_outputs.hpp | 12 + src/physics/mfem_common_integ.cpp | 367 +++++++++++++++++++++++- src/physics/mfem_common_integ.hpp | 104 ++++++- src/utils/l2_transfer_operator.cpp | 114 ++++++-- src/utils/l2_transfer_operator.hpp | 63 ++-- test/unit/test_l2_transfer_operator.cpp | 211 +++++++++++++- test/unit/test_mfem_common_integ.cpp | 215 +++++++++++++- 10 files changed, 1107 insertions(+), 96 deletions(-) diff --git a/mach/pyMach/machSolver.cpp b/mach/pyMach/machSolver.cpp index 5a675746..ea4b39f3 100644 --- a/mach/pyMach/machSolver.cpp +++ b/mach/pyMach/machSolver.cpp @@ -43,36 +43,37 @@ void initSolver(py::module &m) // py::arg("json_options"), // py::arg("comm") = mpi_comm(MPI_COMM_WORLD)) .def("getOptions", &AbstractSolver2::getOptions) - // .def( - // "setState", - // [](AbstractSolver2 &self, - // const std::function &fun, - // const py::array_t &state, - // const std::string &name) - // { - // auto state_vec = npBufferToMFEMVector(state); - // self.setState(fun, state_vec, name); - // }, - // py::arg("fun"), - // py::arg("state"), - // py::arg("name") = "state") + // .def( + // "setState", + // [](AbstractSolver2 &self, + // const std::function &fun, + // const py::array_t &state, + // const std::string &name) + // { + // auto state_vec = npBufferToMFEMVector(state); + // self.setState(fun, state_vec, name); + // }, + // py::arg("fun"), + // py::arg("state"), + // py::arg("name") = "state") .def( "setState", [](AbstractSolver2 &self, - // const std::function &fun, + // const std::function &fun, const std::function &)> &fun, const py::array_t &state, const std::string &name) { - auto cpp_fun = [&fun](const mfem::Vector &x){ - py::array_t py_x{ - x.Size(), /* Buffer dimensions */ + auto cpp_fun = [&fun](const mfem::Vector &x) + { + py::array_t py_x{ + x.Size(), /* Buffer dimensions */ x.GetData() /* Pointer to buffer */ - }; - return fun(py_x); + }; + return fun(py_x); }; auto state_vec = npBufferToMFEMVector(state); - // self.setState(fun, state_vec, name); + // self.setState(fun, state_vec, name); self.setState(cpp_fun, state_vec, name); }, py::arg("fun"), diff --git a/mach/test/test_mach_functional.py b/mach/test/test_mach_functional.py index 8f04bf68..83206174 100644 --- a/mach/test/test_mach_functional.py +++ b/mach/test/test_mach_functional.py @@ -321,14 +321,14 @@ def field_func(x): MachMesh(solver=emSolver), promotes_outputs=["*"]) - # flux = prob.model.add_subsystem("flux_density", - # MachFunctional(solver=emSolver, - # func="flux_density", - # check_partials=True, - # depends=["state", "mesh_coords"]), - # promotes_inputs=[("mesh_coords", "x_em0"), "state"], - # promotes_outputs=["flux_density"]) - # flux.set_check_partial_options(wrt="*", directional=True) + flux = prob.model.add_subsystem("flux_density", + MachFunctional(solver=emSolver, + func="flux_density", + check_partials=True, + depends=["state", "mesh_coords"]), + promotes_inputs=[("mesh_coords", "x_em0"), "state"], + promotes_outputs=["flux_density"]) + flux.set_check_partial_options(wrt="*", directional=True) flux_mag = prob.model.add_subsystem("flux_magnitude", MachFunctional(solver=emSolver, @@ -339,14 +339,14 @@ def field_func(x): promotes_outputs=["flux_magnitude"]) flux_mag.set_check_partial_options(wrt="*", directional=True) - # avg_flux = prob.model.add_subsystem("average_flux_magnitude", - # MachFunctional(solver=emSolver, - # func="average_flux_magnitude", - # check_partials=True, - # depends=["state", "mesh_coords"]), - # promotes_inputs=[("mesh_coords", "x_em0"), "state"], - # promotes_outputs=["average_flux_magnitude"]) - # avg_flux.set_check_partial_options(wrt="*", directional=True) + avg_flux = prob.model.add_subsystem("average_flux_magnitude", + MachFunctional(solver=emSolver, + func="average_flux_magnitude", + check_partials=True, + depends=["state", "mesh_coords"]), + promotes_inputs=[("mesh_coords", "x_em0"), "state"], + promotes_outputs=["average_flux_magnitude"]) + avg_flux.set_check_partial_options(wrt="*", directional=True) max_flux = prob.model.add_subsystem("max_flux_magnitude", MachFunctional(solver=emSolver, @@ -363,6 +363,15 @@ def field_func(x): data = prob.check_partials(form="central") assert_check_partials(data) + # print(data) + + # flux_mag_data = data.pop("flux_magnitude") + # assert_check_partials({"flux_magnitude": flux_mag_data}) + # avg_flux_data = data.pop("average_flux_magnitude") + # assert_check_partials({"average_flux_magnitude": avg_flux_data}) + # max_flux_data = data.pop("max_flux_magnitude") + # assert_check_partials({"max_flux_magnitude": max_flux_data}) + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index 664205e1..d8abd5e1 100644 --- a/src/physics/common_outputs.cpp +++ b/src/physics/common_outputs.cpp @@ -85,6 +85,41 @@ StateAverageFunctional::StateAverageFunctional( } } +double jacobianVectorProduct(AverageMagnitudeCurlState &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) +{ + const MachInputs &inputs = *output.inputs; + double state = calcOutput(output.state_integ, inputs); + double volume = calcOutput(output.volume, inputs); + + auto out_dot = + volume * jacobianVectorProduct(output.state_integ, wrt_dot, wrt); + out_dot -= state * jacobianVectorProduct(output.volume, wrt_dot, wrt); + out_dot /= pow(volume, 2); + return out_dot; +} + +void vectorJacobianProduct(AverageMagnitudeCurlState &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + const MachInputs &inputs = *output.inputs; + double state = calcOutput(output.state_integ, inputs); + double volume = calcOutput(output.volume, inputs); + + output.scratch.SetSize(wrt_bar.Size()); + + output.scratch = 0.0; + vectorJacobianProduct(output.state_integ, out_bar, wrt, output.scratch); + wrt_bar.Add(1 / volume, output.scratch); + + output.scratch = 0.0; + vectorJacobianProduct(output.volume, out_bar, wrt, output.scratch); + wrt_bar.Add(-state / pow(volume, 2), output.scratch); +} + AverageMagnitudeCurlState::AverageMagnitudeCurlState( mfem::ParFiniteElementSpace &fes, std::map &fields, diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index e805b7da..0681dba6 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -133,6 +133,7 @@ class AverageMagnitudeCurlState friend void setInputs(AverageMagnitudeCurlState &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.state_integ, inputs); setInputs(output.volume, inputs); } @@ -145,6 +146,15 @@ class AverageMagnitudeCurlState return state / volume; } + friend double jacobianVectorProduct(AverageMagnitudeCurlState &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); + + friend void vectorJacobianProduct(AverageMagnitudeCurlState &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); + AverageMagnitudeCurlState(mfem::ParFiniteElementSpace &fes, std::map &fields) : AverageMagnitudeCurlState(fes, fields, {}) @@ -157,6 +167,8 @@ class AverageMagnitudeCurlState private: FunctionalOutput state_integ; FunctionalOutput volume; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; class IEAggregateFunctional diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index 435fffc7..b5a5a50b 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -12,7 +12,23 @@ double VolumeIntegrator::GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) { - const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } double vol = 0.0; for (int i = 0; i < ir->GetNPoints(); ++i) @@ -30,6 +46,91 @@ double VolumeIntegrator::GetElementEnergy(const mfem::FiniteElement &el, return vol; } +void VolumeIntegratorMeshSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); + + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(mesh_trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (mesh_el.Space() == FunctionSpace::Pk) + { + return 2 * mesh_el.GetOrder() - 2; + } + else + { + return 2 * mesh_el.GetOrder(); + } + }(); + + ir = &IntRules.Get(mesh_el.GetGeomType(), order); + } + + auto *rho = integ.rho; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + mesh_trans.SetIntPoint(&ip); + + double trans_weight = mesh_trans.Weight(); + + double w = ip.weight * trans_weight; + + /// Start reverse pass... + /// vol += val; + double vol_bar = 1.0; + double val_bar = vol_bar; + + double w_bar = 0.0; + PointMat_bar = 0.0; + if (rho != nullptr) + { + double s = rho->Eval(mesh_trans, ip); + + /// val = w * s; + double s_bar = val_bar * w; + w_bar += val_bar * s; + + /// double s = rho->Eval(mesh_trans, ip); + rho->EvalRevDiff(s_bar, mesh_trans, ip, PointMat_bar); + } + else + { + /// val = w; + w_bar += val_bar; + } + + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; + + /// double trans_weight = mesh_trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + double StateIntegrator::GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) @@ -64,8 +165,8 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( curlshape.SetSize(ndof, curl_dim); curlshape_dFt.SetSize(ndof, curl_dim); - double b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, curl_dim); + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); const auto *ir = IntRule; if (ir == nullptr) @@ -74,7 +175,7 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { @@ -88,12 +189,12 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( double fun = 0.0; for (int i = 0; i < ir->GetNPoints(); i++) { - b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - double w = ip.weight * trans.Weight(); + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; if (space_dim == 3) { @@ -105,14 +206,258 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( el.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); } - curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans.Weight(); - fun += b_mag * w; + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans.Weight(); + fun += curl_mag * w; } return fun; } +void MagnitudeCurlStateIntegrator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int space_dim = trans.GetSpaceDim(); + int curl_dim = space_dim; + +#ifdef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape; + mfem::DenseMatrix curlshape_dFt; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + const auto *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + // const double curl_mag = curl_vec_norm / trans.Weight(); + // fun += curl_mag * w; + + /// Start reverse pass... + /// fun += curl_mag * w; + double fun_bar = 1.0; + + double curl_mag_bar = 0.0; + // double w_bar = 0.0; + curl_mag_bar += fun_bar * w; + // w_bar += fun_bar * curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + // double trans_weight_bar = -curl_mag_bar * curl_vec_norm / + // pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt.AddMult(curl_vec_bar, elfun_bar); + } +} + +void MagnitudeCurlStateIntegratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; +#else + auto &curlshape = integ.curlshape; + auto &curlshape_dFt = integ.curlshape_dFt; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + PointMat_bar.SetSize(curl_dim, mesh_ndof); + + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); + + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + if (space_dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + } + + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; + + // fun += curl_mag * w; + + /// Start reverse pass... + /// fun += curl_mag * w; + double fun_bar = 1.0; + + double curl_mag_bar = 0.0; + double w_bar = 0.0; + curl_mag_bar += fun_bar * w; + w_bar += fun_bar * curl_mag; + + /// const double curl_mag = curl_vec_norm / trans_weight; + double curl_vec_norm_bar = curl_mag_bar / trans_weight; + double trans_weight_bar = + -curl_mag_bar * curl_vec_norm / pow(trans_weight, 2); + + /// const double curl_vec_norm = curl_vec.Norml2(); + curl_vec_bar = 0.0; + curl_vec_bar.Add(curl_vec_norm_bar / curl_vec_norm, curl_vec); + + PointMat_bar = 0.0; + if (dim == 3) + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, ndof); + MultVWt(curl_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt_bar.SetSize(ndof, curl_dim); + MultVWt(elfun, curl_vec_bar, curlshape_dFt_bar); + + /// Mult(curlshape, trans.AdjugateJacobian(), curlshape_dFt); + double adj_bar_buffer[9] = {}; + DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + MultAtB(curlshape, curlshape_dFt_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + } + + /// const double w = ip.weight * trans_weight; + trans_weight_bar += w_bar * ip.weight; + + // double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setOptions(IEAggregateIntegratorNumerator &integ, const nlohmann::json &options) { @@ -361,7 +706,7 @@ void IECurlMagnitudeAggregateIntegratorNumeratorMeshSens:: ElementTransformation &mesh_trans, Vector &mesh_coords_bar) { -const int element = mesh_trans.ElementNo; + const int element = mesh_trans.ElementNo; const auto &el = *state.FESpace()->GetFE(element); auto &trans = *state.FESpace()->GetElementTransformation(element); diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 15949d4b..0ace4e33 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -1,7 +1,6 @@ #ifndef MACH_MFEM_COMMON_INTEG #define MACH_MFEM_COMMON_INTEG -#include #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -17,13 +16,61 @@ class VolumeIntegrator : public mfem::NonlinearFormIntegrator mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override + { + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + } + VolumeIntegrator(mfem::Coefficient *rho = nullptr) : rho(rho) { } private: /// Optional density coefficient to get mass mfem::Coefficient *rho; + + friend class VolumeIntegratorMeshSens; }; +class VolumeIntegratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + VolumeIntegratorMeshSens(VolumeIntegrator &integ) : integ(integ) { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// reference to primal integrator + VolumeIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addSensitivityIntegrator( + VolumeIntegrator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator(new VolumeIntegratorMeshSens(primal_integ)); +} + class StateIntegrator : public mfem::NonlinearFormIntegrator { public: @@ -44,13 +91,66 @@ class MagnitudeCurlStateIntegrator : public mfem::NonlinearFormIntegrator mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + private: #ifndef MFEM_THREAD_SAFE mfem::DenseMatrix curlshape; mfem::DenseMatrix curlshape_dFt; #endif + + friend class MagnitudeCurlStateIntegratorMeshSens; }; +class MagnitudeCurlStateIntegratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + MagnitudeCurlStateIntegratorMeshSens(mfem::GridFunction &state, + MagnitudeCurlStateIntegrator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating integrator + mfem::GridFunction &state; + /// reference to primal integrator + MagnitudeCurlStateIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix curlshape_dFt_bar; + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun; +#endif +}; + +inline void addSensitivityIntegrator( + MagnitudeCurlStateIntegrator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator(new MagnitudeCurlStateIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); +} + class IEAggregateIntegratorNumerator : public mfem::NonlinearFormIntegrator { public: @@ -147,7 +247,7 @@ class IECurlMagnitudeAggregateIntegratorNumeratorMeshSens mfem::Vector &mesh_coords_bar) override; private: - /// state vector for evaluating force + /// state vector for evaluating integrator mfem::GridFunction &state; /// reference to primal integrator IECurlMagnitudeAggregateIntegratorNumerator &integ; diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index 45f39844..c366667b 100644 --- a/src/utils/l2_transfer_operator.cpp +++ b/src/utils/l2_transfer_operator.cpp @@ -342,7 +342,7 @@ class CurlOperator : public mach::L2TransferOperation int output_dof = output_fe.GetDof(); int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + int curl_dim = space_dim; mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); @@ -358,8 +358,20 @@ class CurlOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + mfem::DenseMatrix scratch(state_dof, curl_dim); + + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), scratch); + mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + scratch.GradToCurl(tmp); + } curlshape_dFt.MultTranspose(el_state, curl_vec); curl_vec /= trans.Weight(); @@ -382,7 +394,7 @@ class CurlOperator : public mach::L2TransferOperation int output_dof = output_fe.GetDof(); int space_dim = trans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + int curl_dim = space_dim; mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); @@ -403,8 +415,22 @@ class CurlOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + // state_fe.CalcCurlShape(ip, curlshape); + // MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + mfem::DenseMatrix scratch(state_dof, curl_dim); + + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), scratch); + mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + scratch.GradToCurl(tmp); + } curlshape_dFt.MultTranspose(el_state, curl_vec); curl_vec /= trans.Weight(); @@ -457,7 +483,7 @@ class CurlOperator : public mach::L2TransferOperation auto &isotrans = dynamic_cast(trans); const auto &mesh_fe = *isotrans.GetFE(); int space_dim = isotrans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + int curl_dim = space_dim; int mesh_dof = mesh_fe.GetDof(); int state_dof = state_fe.GetDof(); @@ -465,6 +491,7 @@ class CurlOperator : public mach::L2TransferOperation mfem::DenseMatrix curlshape(state_dof, curl_dim); mfem::DenseMatrix curlshape_dFt(state_dof, curl_dim); + mfem::DenseMatrix scratch(state_dof, curl_dim); mfem::Vector adj_shape(output_dof); double curl_vec_buffer[3]; @@ -483,7 +510,8 @@ class CurlOperator : public mach::L2TransferOperation mfem::Vector adj_shape_bar(output_dof); - mfem::DenseMatrix curlshape_dFt_bar(curl_dim, state_dof); + mfem::DenseMatrix scratch_bar(state_dof, curl_dim); + mfem::DenseMatrix curlshape_dFt_bar; mesh_coords_bar.SetSize(mesh_dof * space_dim); mesh_coords_bar = 0.0; @@ -494,8 +522,18 @@ class CurlOperator : public mach::L2TransferOperation const auto &ip = ir.IntPoint(i); trans.SetIntPoint(&ip); - state_fe.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + if (space_dim == 3) + { + state_fe.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + state_fe.CalcDShape(ip, curlshape); + Mult(curlshape, trans.AdjugateJacobian(), scratch); + mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + scratch.GradToCurl(tmp); + } curlshape_dFt.MultTranspose(el_state, curl_vec); output_fe.CalcPhysShape(trans, adj_shape); @@ -535,18 +573,54 @@ class CurlOperator : public mach::L2TransferOperation /// output_fe.CalcPhysShape(trans, adj_shape); output_fe.CalcPhysShapeRevDiff(trans, adj_shape_bar, PointMat_bar); - /// curlshape_dFt.MultTranspose(el_state, curl_vec); - curlshape_dFt_bar = 0.0; - AddMultVWt(curl_vec_bar, el_state, curlshape_dFt_bar); + // /// curlshape_dFt.MultTranspose(el_state, curl_vec); + // curlshape_dFt_bar = 0.0; + // AddMultVWt(curl_vec_bar, el_state, curlshape_dFt_bar); - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; - mfem::DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_bar); - isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + // /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + // double jac_bar_buffer[9]; + // mfem::DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + // jac_bar = 0.0; + // AddMult(curlshape_dFt_bar, curlshape, jac_bar); + // isotrans.JacobianRevDiff(jac_bar, PointMat_bar); - /// state_fe.CalcCurlShape(ip, curlshape); + if (space_dim == 3) + { + /// curlshape_dFt.MultTranspose(el_state, curl_vec); + // transposed dimensions of curlshape_dFt + // so I don't have to transpose jac_bar later + curlshape_dFt_bar.SetSize(curl_dim, state_dof); + MultVWt(curl_vec_bar, el_state, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + mfem::DenseMatrix jac_bar(jac_bar_buffer, space_dim, space_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + + /// state_fe.CalcCurlShape(ip, curlshape); + } + else // Dealing with scalar H1 field representing Az + { + /// curlshape_dFt.MultTranspose(el_state, curl_vec); + curlshape_dFt_bar.SetSize(state_dof, curl_dim); + MultVWt(el_state, curl_vec_bar, curlshape_dFt_bar); + + /// mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + /// scratch.GradToCurl(tmp); + mfem::DenseMatrix tmp(scratch_bar.GetData(), space_dim * state_dof, 1); + curlshape_dFt_bar.GradToCurl(tmp); + scratch_bar *= -1.0; + + /// Mult(curlshape, trans.AdjugateJacobian(), scratch); + double adj_bar_buffer[9] = {}; + mfem::DenseMatrix adj_bar(adj_bar_buffer, space_dim, space_dim); + MultAtB(curlshape, scratch_bar, adj_bar); + isotrans.AdjugateJacobianRevDiff(adj_bar, PointMat_bar); + + /// state_fe.CalcDShape(ip, curlshape); + } /// insert PointMat_bar into mesh_coords_bar for (int j = 0; j < mesh_dof; ++j) diff --git a/src/utils/l2_transfer_operator.hpp b/src/utils/l2_transfer_operator.hpp index 3c15db54..39c4ac66 100644 --- a/src/utils/l2_transfer_operator.hpp +++ b/src/utils/l2_transfer_operator.hpp @@ -110,6 +110,42 @@ class L2TransferOperator std::unique_ptr operation; }; +inline void setInputs(L2TransferOperator &output, const MachInputs &inputs) +{ + mfem::Vector state_tv; + setVectorFromInputs(inputs, "state", state_tv); + if (state_tv.Size() > 0) + { + output.state.distributeSharedDofs(state_tv); + } + mfem::Vector mesh_coords_tv; + setVectorFromInputs(inputs, "mesh_coords", mesh_coords_tv); + if (mesh_coords_tv.Size() > 0) + { + output.mesh_coords.distributeSharedDofs(mesh_coords_tv); + } +} + +inline double calcOutput(L2TransferOperator &output, const MachInputs &inputs) +{ + return NAN; +} + +inline void calcOutput(L2TransferOperator &output, + const MachInputs &inputs, + mfem::Vector &out_vec) +{ + output.apply(inputs, out_vec); +} + +inline void vectorJacobianProduct(L2TransferOperator &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + output.vectorJacobianProduct(out_bar, wrt, wrt_bar); +} + /// Conveniece class that wraps the projection of an H1 state to its DG /// representation class ScalarL2IdentityProjection : public L2TransferOperator @@ -156,9 +192,14 @@ class L2CurlMagnitudeProjection : public L2TransferOperator L2CurlMagnitudeProjection(FiniteElementState &state, FiniteElementState &mesh_coords, FiniteElementState &output); + friend inline int getSize(const L2CurlMagnitudeProjection &output) + { + return output.output.space().GetTrueVSize(); + } + friend void setInputs(L2CurlMagnitudeProjection &output, const MachInputs &inputs); }; -inline void setInputs(L2TransferOperator &output, const MachInputs &inputs) +inline void setInputs(L2CurlMagnitudeProjection &output, const MachInputs &inputs) { mfem::Vector state_tv; setVectorFromInputs(inputs, "state", state_tv); @@ -174,11 +215,6 @@ inline void setInputs(L2TransferOperator &output, const MachInputs &inputs) } } -inline double calcOutput(L2TransferOperator &output, const MachInputs &inputs) -{ - return NAN; -} - inline void calcOutput(L2CurlMagnitudeProjection &output, const MachInputs &inputs, mfem::Vector &out_vec) @@ -194,21 +230,6 @@ inline void vectorJacobianProduct(L2CurlMagnitudeProjection &output, output.vectorJacobianProduct(out_bar, wrt, wrt_bar); } -inline void calcOutput(L2TransferOperator &output, - const MachInputs &inputs, - mfem::Vector &out_vec) -{ - output.apply(inputs, out_vec); -} - -inline void vectorJacobianProduct(L2TransferOperator &output, - const mfem::Vector &out_bar, - const std::string &wrt, - mfem::Vector &wrt_bar) -{ - output.vectorJacobianProduct(out_bar, wrt, wrt_bar); -} - inline void calcOutput(L2CurlProjection &output, const MachInputs &inputs, mfem::Vector &out_vec) diff --git a/test/unit/test_l2_transfer_operator.cpp b/test/unit/test_l2_transfer_operator.cpp index 3f2a1e30..097d38dc 100644 --- a/test/unit/test_l2_transfer_operator.cpp +++ b/test/unit/test_l2_transfer_operator.cpp @@ -751,6 +751,108 @@ TEST_CASE("L2CurlProjection::vectorJacobianProduct wrt state") REQUIRE(dout_dstate_v == Approx(dout_dstate_v_fd).margin(1e-8)); } +TEST_CASE("L2CurlProjection::vectorJacobianProduct wrt state - 2D") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + int nxy = 4; + int nz = 1; + int num_edge = 2; + auto smesh = mfem::Mesh::MakeCartesian2D(num_edge, num_edge, + mfem::Element::TRIANGLE); + auto mesh = mfem::ParMesh(MPI_COMM_WORLD, smesh); + mesh.EnsureNodes(); + const auto dim = mesh.Dimension(); + + const auto p = 2; + + mach::FiniteElementState state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "H1"}}); + + mach::FiniteElementState dg_state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "DG"}}, + dim); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + + mach::FiniteElementState mesh_coords(mesh, *mesh_fespace); + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::L2CurlProjection op(state, mesh_coords, dg_state); + + mfem::Vector state_tv(state.space().GetTrueVSize()); + mfem::Vector dg_state_tv(dg_state.space().GetTrueVSize()); + + mfem::FunctionCoefficient state_coeff([&](const mfem::Vector &x) + { + return uniform_rand(gen); + }); + state.project(state_coeff, state_tv); + mfem::Vector state_tv_copy(state_tv); + + mfem::Vector out_bar(dg_state.space().GetTrueVSize()); + for (int i = 0; i < out_bar.Size(); ++i) + { + out_bar(i) = uniform_rand(gen); + } + mfem::Vector state_pert(state.space().GetTrueVSize()); + for (int i = 0; i < state_pert.Size(); ++i) + { + state_pert(i) = uniform_rand(gen); + } + + mfem::Vector state_bar(state.space().GetTrueVSize()); + state_bar = 0.0; + setInputs(op, {{"state", state_tv}}); + op.vectorJacobianProduct(out_bar, "state", state_bar); + + auto dout_dstate_v_local = state_pert * state_bar; + double dout_dstate_v; + MPI_Allreduce(&dout_dstate_v_local, + &dout_dstate_v, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + // now compute the finite-difference approximation... + auto delta = 1e-5; + double dout_dstate_v_fd_local = 0.0; + + add(state_tv, delta, state_pert, state_tv); + op.apply(state_tv, dg_state_tv); + dout_dstate_v_fd_local += out_bar * dg_state_tv; + + add(state_tv, -2*delta, state_pert, state_tv); + op.apply(state_tv, dg_state_tv); + dout_dstate_v_fd_local -= out_bar * dg_state_tv; + + dout_dstate_v_fd_local /= 2*delta; + double dout_dstate_v_fd; + MPI_Allreduce(&dout_dstate_v_fd_local, + &dout_dstate_v_fd, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + auto rank = state.space().GetMyRank(); + if (rank == 0) + { + std::cout << "dout_dstate_v: " << dout_dstate_v << "\n"; + std::cout << "dout_dstate_v_fd: " << dout_dstate_v_fd << "\n"; + } + + REQUIRE(dout_dstate_v == Approx(dout_dstate_v_fd).margin(1e-8)); +} + TEST_CASE("L2CurlProjection::vectorJacobianProduct wrt mesh_coords") { std::default_random_engine gen; @@ -864,6 +966,113 @@ TEST_CASE("L2CurlProjection::vectorJacobianProduct wrt mesh_coords") REQUIRE(dout_dmesh_v == Approx(dout_dmesh_v_fd).margin(1e-8)); } +TEST_CASE("L2CurlProjection::vectorJacobianProduct wrt mesh_coords - 2D") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + // generate a 8 element mesh + int num_edge = 2; + auto smesh = mfem::Mesh::MakeCartesian2D(num_edge, num_edge, + mfem::Element::TRIANGLE); + auto mesh = mfem::ParMesh(MPI_COMM_WORLD, smesh); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + const auto p = 4; + + /// create new state vector copying the mesh's fe space + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto &mesh_fespace = *mesh_gf.ParFESpace(); + mach::FiniteElementState mesh_coords(mesh, mesh_fespace, "mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::FiniteElementState state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "H1"}}); + + mach::FiniteElementState dg_state(mesh, nlohmann::json{ + {"degree", p}, + {"basis-type", "DG"}}, + dim); + + mach::L2CurlProjection op(state, mesh_coords, dg_state); + + mfem::Vector state_tv(state.space().GetTrueVSize()); + mfem::Vector dg_state_tv(dg_state.space().GetTrueVSize()); + + mfem::FunctionCoefficient state_coeff([&](const mfem::Vector &x) + { + return uniform_rand(gen); + }); + state.project(state_coeff, state_tv); + mfem::Vector state_tv_copy(state_tv); + + mfem::Vector out_bar(dg_state.space().GetTrueVSize()); + for (int i = 0; i < out_bar.Size(); ++i) + { + out_bar(i) = uniform_rand(gen); + } + mfem::Vector mesh_pert(mesh_coords.space().GetTrueVSize()); + for (int i = 0; i < mesh_pert.Size(); ++i) + { + mesh_pert(i) = uniform_rand(gen); + } + + mfem::Vector mesh_coords_bar(mesh_coords.space().GetTrueVSize()); + mesh_coords_bar = 0.0; + setInputs(op, {{"state", state_tv}}); + op.vectorJacobianProduct(out_bar, "mesh_coords", mesh_coords_bar); + + auto dout_dmesh_v_local = mesh_pert * mesh_coords_bar; + double dout_dmesh_v; + MPI_Allreduce(&dout_dmesh_v_local, + &dout_dmesh_v, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + // now compute the finite-difference approximation... + auto delta = 1e-5; + double dout_dmesh_v_fd_local = 0.0; + + add(mesh_coords_tv, delta, mesh_pert, mesh_coords_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); // update mesh nodes + op.apply(state_tv, dg_state_tv); + dout_dmesh_v_fd_local += out_bar * dg_state_tv; + + add(mesh_coords_tv, -2*delta, mesh_pert, mesh_coords_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); // update mesh nodes + op.apply(state_tv, dg_state_tv); + dout_dmesh_v_fd_local -= out_bar * dg_state_tv; + + dout_dmesh_v_fd_local /= 2*delta; + double dout_dmesh_v_fd; + MPI_Allreduce(&dout_dmesh_v_fd_local, + &dout_dmesh_v_fd, + 1, + MPI_DOUBLE, + MPI_SUM, + state.space().GetComm()); + + auto rank = state.space().GetMyRank(); + if (rank == 0) + { + std::cout << "dout_dmesh_v: " << dout_dmesh_v << "\n"; + std::cout << "dout_dmesh_v_fd: " << dout_dmesh_v_fd << "\n"; + } + + REQUIRE(dout_dmesh_v == Approx(dout_dmesh_v_fd).margin(1e-8)); +} + TEST_CASE("L2CurlMagnitudeProjection::apply") { int nxy = 2; @@ -1357,4 +1566,4 @@ TEST_CASE("L2CurlMagnitudeProjection::vectorJacobianProduct wrt mesh_coords - 2D } REQUIRE(dout_dmesh_v == Approx(dout_dmesh_v_fd).margin(1e-8)); -} \ No newline at end of file +} diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index ddb534b7..9520a419 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -78,6 +78,95 @@ TEST_CASE("VolumeIntegrator::GetElementEnergy (3D)") REQUIRE(volume == Approx(2.0).margin(1e-10)); } +TEST_CASE("VolumeIntegratorMeshSens::AssembleRHSElementVect (2D)") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + } + return q; + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += 2 * x(i) * q_bar; + } + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + mesh.SetCurvature(p); + + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::VolumeIntegrator(&model); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::VolumeIntegratorMeshSens(*integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("StateIntegrator::GetElementEnergy (3D)") { using namespace mfem; @@ -114,7 +203,7 @@ TEST_CASE("StateIntegrator::GetElementEnergy (3D)") REQUIRE(rms == Approx(sqrt(2)/2).margin(1e-10)); } -TEST_CASE("IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector") +TEST_CASE("MagnitudeCurlStateIntegrator::AssembleElementVector") { using namespace mfem; using namespace electromag_data; @@ -128,8 +217,126 @@ TEST_CASE("IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector") mesh.EnsureNodes(); const auto dim = mesh.SpaceDimension(); - NonLinearCoefficient nu; - // LinearCoefficient nu; + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::MagnitudeCurlStateIntegrator); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("MagnitudeCurlStateIntegratorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::MagnitudeCurlStateIntegrator; + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::MagnitudeCurlStateIntegratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + +TEST_CASE("IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); for (int p = 1; p <= 4; ++p) { @@ -188,8 +395,6 @@ TEST_CASE("IECurlMagnitudeAggregateIntegratorNumeratorMeshSens::AssembleRHSEleme mesh.EnsureNodes(); const auto dim = mesh.SpaceDimension(); - NonLinearCoefficient nu; - for (int p = 1; p <= 4; ++p) { DYNAMIC_SECTION("...for degree p = " << p) From 31ec4b2cac37942e812163e6e83b075e0aed2024 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 13 Sep 2022 20:57:54 -0400 Subject: [PATCH 47/72] fix test_common_outputs test by adding mesh_coords to fieldsf --- src/utils/l2_transfer_operator.cpp | 20 ++++++++++++-------- src/utils/l2_transfer_operator.hpp | 6 ++++-- test/unit/test_common_outputs.cpp | 28 +++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index c366667b..0139682c 100644 --- a/src/utils/l2_transfer_operator.cpp +++ b/src/utils/l2_transfer_operator.cpp @@ -369,7 +369,8 @@ class CurlOperator : public mach::L2TransferOperation state_fe.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), scratch); - mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + mfem::DenseMatrix tmp( + curlshape_dFt.GetData(), space_dim * state_dof, 1); scratch.GradToCurl(tmp); } curlshape_dFt.MultTranspose(el_state, curl_vec); @@ -425,10 +426,11 @@ class CurlOperator : public mach::L2TransferOperation else { mfem::DenseMatrix scratch(state_dof, curl_dim); - + state_fe.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), scratch); - mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + mfem::DenseMatrix tmp( + curlshape_dFt.GetData(), space_dim * state_dof, 1); scratch.GradToCurl(tmp); } curlshape_dFt.MultTranspose(el_state, curl_vec); @@ -528,10 +530,11 @@ class CurlOperator : public mach::L2TransferOperation MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else - { + { state_fe.CalcDShape(ip, curlshape); Mult(curlshape, trans.AdjugateJacobian(), scratch); - mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); + mfem::DenseMatrix tmp( + curlshape_dFt.GetData(), space_dim * state_dof, 1); scratch.GradToCurl(tmp); } curlshape_dFt.MultTranspose(el_state, curl_vec); @@ -607,9 +610,10 @@ class CurlOperator : public mach::L2TransferOperation curlshape_dFt_bar.SetSize(state_dof, curl_dim); MultVWt(el_state, curl_vec_bar, curlshape_dFt_bar); - /// mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * state_dof, 1); - /// scratch.GradToCurl(tmp); - mfem::DenseMatrix tmp(scratch_bar.GetData(), space_dim * state_dof, 1); + /// mfem::DenseMatrix tmp(curlshape_dFt.GetData(), space_dim * + /// state_dof, 1); scratch.GradToCurl(tmp); + mfem::DenseMatrix tmp( + scratch_bar.GetData(), space_dim * state_dof, 1); curlshape_dFt_bar.GradToCurl(tmp); scratch_bar *= -1.0; diff --git a/src/utils/l2_transfer_operator.hpp b/src/utils/l2_transfer_operator.hpp index 39c4ac66..fadadc39 100644 --- a/src/utils/l2_transfer_operator.hpp +++ b/src/utils/l2_transfer_operator.hpp @@ -196,10 +196,12 @@ class L2CurlMagnitudeProjection : public L2TransferOperator { return output.output.space().GetTrueVSize(); } - friend void setInputs(L2CurlMagnitudeProjection &output, const MachInputs &inputs); + friend void setInputs(L2CurlMagnitudeProjection &output, + const MachInputs &inputs); }; -inline void setInputs(L2CurlMagnitudeProjection &output, const MachInputs &inputs) +inline void setInputs(L2CurlMagnitudeProjection &output, + const MachInputs &inputs) { mfem::Vector state_tv; setVectorFromInputs(inputs, "state", state_tv); diff --git a/test/unit/test_common_outputs.cpp b/test/unit/test_common_outputs.cpp index 12bab6e8..93e80758 100644 --- a/test/unit/test_common_outputs.cpp +++ b/test/unit/test_common_outputs.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "catch.hpp" #include "mfem.hpp" @@ -28,6 +30,14 @@ TEST_CASE("StateAverageFunctional::calcOutput (3D)") std::forward_as_tuple("state"), std::forward_as_tuple(mesh, fes, "state")); + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace( + std::piecewise_construct, + std::forward_as_tuple("mesh_coords"), + std::forward_as_tuple(mesh, *mesh_fespace, "mesh_coords")); + mach::StateAverageFunctional out(fes, fields); auto &state = fields.at("state"); @@ -63,6 +73,14 @@ TEST_CASE("IEAggregateFunctional::calcOutput") std::forward_as_tuple("state"), std::forward_as_tuple(mesh, fes, "state")); + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace( + std::piecewise_construct, + std::forward_as_tuple("mesh_coords"), + std::forward_as_tuple(mesh, *mesh_fespace, "mesh_coords")); + auto &state = fields.at("state"); mfem::Vector state_tv(state.space().GetTrueVSize()); @@ -110,6 +128,14 @@ TEST_CASE("IECurlMagnitudeAggregateFunctional::calcOutput") std::forward_as_tuple("state"), std::forward_as_tuple(mesh, fes, "state")); + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace( + std::piecewise_construct, + std::forward_as_tuple("mesh_coords"), + std::forward_as_tuple(mesh, *mesh_fespace, "mesh_coords")); + auto &state = fields.at("state"); mfem::Vector state_tv(state.space().GetTrueVSize()); @@ -130,7 +156,7 @@ TEST_CASE("IECurlMagnitudeAggregateFunctional::calcOutput") double max_state = calcOutput(out, inputs); /// Should be sqrt(sin(1.0)^2 + 1.0) - REQUIRE(max_state == Approx(1.3026749725)); + REQUIRE(max_state == Approx(1.2908573815)); output_opts["rho"] = 1.0; setOptions(out, output_opts); From fdcbf1819e24f778c1aa94e29be542d70c2fdc71 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Thu, 15 Sep 2022 21:35:23 -0400 Subject: [PATCH 48/72] got all solver partials and totals working and passing through OpenMDAO. Residual partials wrt current density are a little bit hacky. Need to come up with a real way to take the derivative of an integrator wrt a coefficient, and then the coefficient wrt whatever, but haven't thought of a good way to do it yet --- mach/mach_state.py | 2 +- mach/test/test_mach_state.py | 247 +++++++++++++----- src/common/mach_linearform.cpp | 34 ++- src/common/mach_linearform.hpp | 2 + .../electromagnetics/electromag_integ.hpp | 33 ++- .../electromagnetics/magnetostatic.cpp | 1 + .../magnetostatic_residual.cpp | 55 +++- src/physics/mfem_common_integ.cpp | 130 +++++++++ src/physics/mfem_common_integ.hpp | 78 ++++++ src/physics/pde_solver.cpp | 1 + test/regression/test_magnetostatic_box2d.cpp | 4 +- test/unit/test_mfem_common_integ.cpp | 96 +++++++ 12 files changed, 587 insertions(+), 96 deletions(-) diff --git a/mach/mach_state.py b/mach/mach_state.py index 378048eb..f9e92b78 100644 --- a/mach/mach_state.py +++ b/mach/mach_state.py @@ -117,7 +117,7 @@ def apply_nonlinear(self, inputs, outputs, residuals): input_dict = dict(zip(inputs.keys(), inputs.values())) input_dict.update(dict(zip(outputs.keys(), outputs.values()))) - input_dict.update(self.vectors) + input_dict.update(self.vectors) residual = self.vectors["state_res"] solver.calcResidual(input_dict, residual) diff --git a/mach/test/test_mach_state.py b/mach/test/test_mach_state.py index 1de6f21a..5712d125 100644 --- a/mach/test/test_mach_state.py +++ b/mach/test/test_mach_state.py @@ -5,72 +5,75 @@ from mach import PDESolver, MachState, MachMesh -em_options = { - "mesh": { - # "file": "data/testOMMach/parallel_wires.smb", - # "model-file": "data/testOMMach/parallel_wires.egads", - "file": "data/box.mesh", - "refine": 0 - }, - "space-dis": { - "basis-type": "nedelec", - "degree": 1 - }, - "time-dis": { - "steady": True, - }, - "nonlin-solver": { - "type": "newton", - "printlevel": 2, - "maxiter": 5, - "reltol": 1e-6, - "abstol": 1e-6 - }, - "lin-solver": { - "type": "minres", - "printlevel": 1, - "maxiter": 100, - "abstol": 1e-14, - "reltol": 1e-14 - }, - "adj-solver": { - "type": "minres", - "printlevel": 1, - "maxiter": 100, - "abstol": 1e-14, - "reltol": 1e-14 - }, - "lin-prec": { - "printlevel": -1 - }, - "components": { - # "wires": { - # "material": "copperwire", - # "attrs": [1, 2], - # "linear": True - # }, - "attr1": { - "material": "box1", - "attr": 1, - "linear": True +class TestEMState(unittest.TestCase): + em_options = { + "mesh": { + # "file": "data/testOMMach/parallel_wires.smb", + # "model-file": "data/testOMMach/parallel_wires.egads", + "file": "data/box.mesh", + "refine": 0 }, - "attr2": { - "material": "box2", - "attr": 2, - "linear": True - } - }, - "bcs": { - "essential": "all" - }, - "current": { - "wires": { - "z": [1, 2] + "space-dis": { + "basis-type": "nedelec", + "degree": 1 + }, + "time-dis": { + "steady": True, + }, + "nonlin-solver": { + "type": "newton", + "printlevel": 2, + "maxiter": 5, + "reltol": 1e-6, + "abstol": 1e-6 + }, + "lin-solver": { + "type": "minres", + "printlevel": 1, + "maxiter": 100, + "abstol": 1e-14, + "reltol": 1e-14 + }, + "adj-solver": { + "type": "minres", + "printlevel": 1, + "maxiter": 100, + "abstol": 1e-14, + "reltol": 1e-14 + }, + "lin-prec": { + "printlevel": -1 + }, + "components": { + # "wires": { + # "material": "copperwire", + # "attrs": [1, 2], + # "linear": True + # }, + "attr1": { + "attr": 1, + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + }, + }, + "attr2": { + "attr": 2, + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + }, + } + }, + "bcs": { + "essential": "all" + }, + "current": { + "wires": { + "z": [1, 2] + } } } -} - -class TestEMState(unittest.TestCase): # def test_forward(self): # prob = om.Problem() @@ -94,17 +97,17 @@ class TestEMState(unittest.TestCase): def test_partials(self): prob = om.Problem() - emSolver = PDESolver(type="magnetostatic", solver_options=em_options, comm=prob.comm) + emSolver = PDESolver(type="magnetostatic", solver_options=self.em_options, comm=prob.comm) prob.model.add_subsystem("ivc", MachMesh(solver=emSolver), promotes_outputs=["*"]) solver = prob.model.add_subsystem("em_solver", - MachState(solver=emSolver, - depends=["current_density:wires"], - check_partials=True), - promotes_inputs=["current_density:wires", ("mesh_coords", "x_em0")], - promotes_outputs=["state"]) + MachState(solver=emSolver, + depends=["current_density:wires", "mesh_coords"], + check_partials=True), + promotes_inputs=["current_density:wires", ("mesh_coords", "x_em0")], + promotes_outputs=["state"]) solver.set_check_partial_options(wrt="*", directional=False, form="central") @@ -122,14 +125,14 @@ def test_partials(self): def test_totals(self): prob = om.Problem() - emSolver = PDESolver(type="magnetostatic", solver_options=em_options, comm=prob.comm) + emSolver = PDESolver(type="magnetostatic", solver_options=self.em_options, comm=prob.comm) prob.model.add_subsystem("ivc", MachMesh(solver=emSolver), promotes_outputs=["*"]) prob.model.add_subsystem("em_solver", MachState(solver=emSolver, - depends=["current_density:wires"], + depends=["current_density:wires", "mesh_coords"], check_partials=True), promotes_inputs=["current_density:wires", ("mesh_coords", "x_em0")], promotes_outputs=["state"]) @@ -142,5 +145,107 @@ def test_totals(self): data = prob.check_totals(of=["state"], wrt=["current_density:wires"]) assert_check_totals(data, atol=1e-6, rtol=1e-6) +class TestEMState2D(unittest.TestCase): + square_options = { + "mesh": { + "file": "data/simple_square.mesh", + "refine": 0 + }, + "space-dis": { + "basis-type": "h1", + "degree": 1 + }, + "lin-solver": { + "type": "pcg", + "printlevel": 1, + "maxiter": 100, + "abstol": 1e-14, + "reltol": 1e-14 + }, + "nonlin-solver": { + "type": "newton", + "printlevel": 1, + "maxiter": 5, + "reltol": 1e-12, + "abstol": 1e-12 + }, + "components": { + "ring": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + }, + } + }, + "bcs": { + "essential": "all" + }, + "current": { + "test": { + "z": [1] + } + } + } + + def test_partials(self): + prob = om.Problem() + + emSolver = PDESolver(type="magnetostatic", solver_options=self.square_options, comm=prob.comm) + + prob.model.add_subsystem("ivc", + MachMesh(solver=emSolver), + promotes_outputs=["*"]) + solver = prob.model.add_subsystem("em_solver", + MachState(solver=emSolver, + depends=["current_density:test", "mesh_coords"], + check_partials=True), + promotes_inputs=["current_density:test", ("mesh_coords", "x_em0")], + promotes_outputs=["state"]) + solver.set_check_partial_options(wrt="*", + directional=False, + form="central", + step=1e-5) + + prob.set_solver_print(level=0) + prob.setup() + + state_size = emSolver.getFieldSize("state") + prob["state"] = np.random.randn(state_size) + + data = prob.check_partials() + # om.partial_deriv_plot("state", "state", data, jac_method="J_rev", binary = False) + # om.partial_deriv_plot("state", "mesh_coords", data, jac_method="J_rev", binary = False) + # om.partial_deriv_plot("state", "current_density:test", data,jac_method="J_rev", binary = False) + assert_check_partials(data) + + def test_totals(self): + prob = om.Problem() + emSolver = PDESolver(type="magnetostatic", solver_options=self.square_options, comm=prob.comm) + + prob.model.add_subsystem("ivc", + MachMesh(solver=emSolver), + promotes_outputs=["*"]) + prob.model.add_subsystem("em_solver", + MachState(solver=emSolver, + depends=["current_density:test", "mesh_coords"], + check_partials=True), + promotes_inputs=["current_density:test", ("mesh_coords", "x_em0")], + promotes_outputs=["state"]) + + prob.setup(mode="rev") + state_size = emSolver.getFieldSize("state") + state = np.random.randn(state_size) + + prob["state"] = state + prob["current_density:test"] = 1.0 + prob.run_model() + + data = prob.check_totals(of=["state"], + wrt=["current_density:test", "x_em0"], + form="central", + step=1e-5) + assert_check_totals(data, atol=1e-6, rtol=1e-6) + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/src/common/mach_linearform.cpp b/src/common/mach_linearform.cpp index 18c29b4a..5cbc8d9d 100644 --- a/src/common/mach_linearform.cpp +++ b/src/common/mach_linearform.cpp @@ -16,22 +16,6 @@ int getSize(const MachLinearForm &load) void setInputs(MachLinearForm &load, const MachInputs &inputs) { - // for (const auto &in : inputs) - // { - // const auto &input = in.second; - // if (input.isField()) - // { - // const auto &name = in.first; - // auto it = load.lf_fields->find(name); - // if (it != load.lf_fields->end()) - // { - // auto &field = it->second; - // field.GetTrueVector().SetDataAndSize( - // input.getField(), field.ParFESpace()->GetTrueVSize()); - // field.SetFromTrueVector(); - // } - // } - // } for (const auto &[name, input] : inputs) { if (std::holds_alternative(input)) @@ -121,11 +105,23 @@ void vectorJacobianProduct(MachLinearForm &load, { if (load.rev_sens.count(wrt) != 0) { + load.scratch.SetSize(load_bar.Size()); + load.scratch = load_bar; + const auto &ess_tdof_list = load.getEssentialDofs(); + load.scratch.SetSubVector(ess_tdof_list, 0.0); + + /// Integrators added to rev_sens will reference the adjoint, grid func + /// so we update it here auto &adjoint = load.lf_fields->at("adjoint"); - adjoint.distributeSharedDofs(load_bar); - load.rev_sens.at(wrt).Assemble(); + adjoint.distributeSharedDofs(load.scratch); + + auto &wrt_rev_sens = load.rev_sens.at(wrt); + + wrt_rev_sens.Assemble(); load.scratch.SetSize(wrt_bar.Size()); - load.rev_sens.at(wrt).ParallelAssemble(load.scratch); + load.scratch = 0.0; + wrt_rev_sens.ParallelAssemble(load.scratch); + wrt_bar += load.scratch; } } diff --git a/src/common/mach_linearform.hpp b/src/common/mach_linearform.hpp index b14d768d..a972bd3d 100644 --- a/src/common/mach_linearform.hpp +++ b/src/common/mach_linearform.hpp @@ -95,6 +95,8 @@ class MachLinearForm final template void addBdrFaceIntegrator(T *integrator, mfem::Array &bdr_attr_marker); + const mfem::Array &getEssentialDofs() const { return ess_tdof_list; } + MachLinearForm(mfem::ParFiniteElementSpace &pfes, std::map &fields) : lf(&pfes), scratch(0), lf_fields(&fields) diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index ce85ef0c..6c69bef3 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -128,6 +128,23 @@ class NonlinearDiffusionIntegratorMeshRevSens #endif }; +inline void addSensitivityIntegrator( + NonlinearDiffusionIntegrator &primal_integ, + std::map &fields, + std::map &rev_sens, + std::map &rev_scalar_sens, + std::map &fwd_sens, + std::map &fwd_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + rev_sens.emplace("mesh_coords", &mesh_fes); + rev_sens.at("mesh_coords") + .AddDomainIntegrator(new NonlinearDiffusionIntegratorMeshRevSens( + fields.at("state").gridFunc(), + fields.at("adjoint").gridFunc(), + primal_integ)); +} + class MagnetizationSource2DIntegrator : public mfem::LinearFormIntegrator { public: @@ -158,7 +175,6 @@ class MagnetizationSource2DIntegratorMeshRevSens : public mfem::LinearFormIntegrator { public: - /// \param[in] state - the state to use when evaluating d(psi^T R)/dX /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX /// \param[in] integ - reference to primal integrator MagnetizationSource2DIntegratorMeshRevSens( @@ -191,6 +207,21 @@ class MagnetizationSource2DIntegratorMeshRevSens #endif }; +inline void addSensitivityIntegrator( + MagnetizationSource2DIntegrator &primal_integ, + std::map &fields, + std::map &rev_sens, + std::map &rev_scalar_sens, + std::map &fwd_sens, + std::map &fwd_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + rev_sens.emplace("mesh_coords", &mesh_fes); + rev_sens.at("mesh_coords") + .AddDomainIntegrator(new MagnetizationSource2DIntegratorMeshRevSens( + fields.at("adjoint").gridFunc(), primal_integ)); +} + /// Integrator for (\nu(u)*curl u, curl v) for Nedelec elements class CurlCurlNLFIntegrator : public mfem::NonlinearFormIntegrator { diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index 4dfec56c..da02bca3 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -89,6 +89,7 @@ MagnetostaticSolver::MagnetostaticSolver(MPI_Comm comm, mach::ParaViewLogger paraview("magnetostatic", &mesh()); paraview.registerField("state", fields.at("state").gridFunc()); + paraview.registerField("adjoint", fields.at("adjoint").gridFunc()); addLogger(std::move(paraview), {}); } diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 62a4d157..b0deae77 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -10,9 +10,10 @@ #include "mach_input.hpp" #include "magnetostatic_load.hpp" +#include "mfem_common_integ.hpp" +#include "utils.hpp" #include "magnetostatic_residual.hpp" -#include "utils.hpp" namespace { @@ -135,6 +136,24 @@ void jacobianVectorProduct(MagnetostaticResidual &residual, const std::string &wrt, mfem::Vector &res_dot) { + // if wrt starts with prefix "current_density:" + if (wrt.rfind("current_density:", 0) == 0) + { + residual.current_coeff->cacheCurrentDensity(); + residual.current_coeff->zeroCurrentDensity(); + + MachInputs inputs{{wrt, 1.0}}; + setInputs(*residual.current_coeff, inputs); + + mfem::Vector scratch(res_dot.Size()); + scratch = 0.0; + addLoad(*residual.load, scratch); + residual.current_coeff->resetCurrentDensityFromCache(); + + scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); + res_dot.Add(wrt_dot(0), scratch); + return; + } jacobianVectorProduct(residual.res, wrt_dot, wrt, res_dot); jacobianVectorProduct(*residual.load, wrt_dot, wrt, res_dot); } @@ -143,6 +162,23 @@ double vectorJacobianProduct(MagnetostaticResidual &residual, const mfem::Vector &res_bar, const std::string &wrt) { + // if wrt starts with prefix "current_density:" + if (wrt.rfind("current_density:", 0) == 0) + { + residual.current_coeff->cacheCurrentDensity(); + residual.current_coeff->zeroCurrentDensity(); + + MachInputs inputs{{wrt, 1.0}}; + setInputs(*residual.current_coeff, inputs); + + mfem::Vector scratch(res_bar.Size()); + scratch = 0.0; + addLoad(*residual.load, scratch); + residual.current_coeff->resetCurrentDensityFromCache(); + + scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); + return scratch * res_bar; + } auto wrt_bar = vectorJacobianProduct(residual.res, res_bar, wrt); wrt_bar += vectorJacobianProduct(*residual.load, res_bar, wrt); return wrt_bar; @@ -192,9 +228,24 @@ MagnetostaticResidual::MagnetostaticResidual( current_coeff = std::make_unique( diff_stack, options["current"]); + // MachInputs current_inputs = {{"current_density:phaseA", 0.0}, + // {"current_density:phaseB", 1.0}, + // {"current_density:phaseC", -1.0}}; + // setInputs(*current_coeff, current_inputs); + + // mfem::ParGridFunction j(&fes); + // j.ProjectCoefficient(*current_coeff); + // mfem::ParaViewDataCollection pv("CurrentDensity", fes.GetParMesh()); + // pv.SetPrefixPath("ParaView"); + // pv.SetLevelsOfDetail(3); + // pv.SetDataFormat(mfem::VTKFormat::BINARY); + // pv.SetHighOrderOutput(true); + // pv.RegisterField("CurrentDensity", &j); + // pv.Save(); + auto current_attrs = getCurrentAttributes(options); linear_form.addDomainIntegrator( - new mfem::DomainLFIntegrator(*current_coeff), current_attrs); + new mach::DomainLFIntegrator(*current_coeff), current_attrs); } if (options.contains("magnets")) { diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index b5a5a50b..2a6972a6 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -2107,6 +2107,136 @@ void VectorFEDomainLFCurlIntegratorMeshSens::AssembleRHSElementVect( } } +void DomainLFIntegratorMeshRevSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *adjoint.FESpace()->GetFE(element); + auto &trans = *adjoint.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + // const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + const int curl_dim = space_dim; + + /// get the proper element, transformation, and state vector +#ifdef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector psi; +#endif + auto *dof_tr = adjoint.FESpace()->GetElementVDofs(element, vdofs); + adjoint.GetSubVector(vdofs, psi); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(psi); + } + +#ifdef MFEM_THREAD_SAFE + Vector shape; + Vector shape_bar; + DenseMatrix PointMat_bar; + Vector scratch_bar; +#endif + + shape.SetSize(ndof); + shape_bar.SetSize(ndof); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // double mag_flux_buffer[3] = {}; + // Vector mag_flux(mag_flux_buffer, space_dim); + // double mag_flux_bar_buffer[3] = {}; + // Vector mag_flux_bar(mag_flux_bar_buffer, space_dim); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(mesh_trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; + } + }(); + + if (el.Space() == FunctionSpace::rQk) + { + ir = &RefinedIntRules.Get(el.GetGeomType(), order); + } + else + { + ir = &IntRules.Get(el.GetGeomType(), order); + } + } + + auto &alpha = integ.alpha; + auto &F = integ.F; + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + double w = alpha * ip.weight * trans_weight; + + double val = F.Eval(trans, ip); + + el.CalcPhysShape(trans, shape); + const double psi_dot_shape = psi * shape; + + /// dummy functional for adjoint-weighted residual + // fun += val * psi_dot_shape * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun += val * psi_dot_shape * w; + double val_bar = fun_bar * psi_dot_shape * w; + double psi_dot_shape_bar = fun_bar * val * w; + double w_bar = fun_bar * val * psi_dot_shape; + + /// const double psi_dot_shape = psi * shape; + shape_bar = 0.0; + shape_bar.Add(psi_dot_shape_bar, psi); + + /// el.CalcPhysShape(trans, shape); + PointMat_bar = 0.0; + el.CalcPhysShapeRevDiff(trans, shape_bar, PointMat_bar); + + /// double val = F.Eval(trans, ip); + F.EvalRevDiff(val_bar, trans, ip, PointMat_bar); + + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * alpha * ip.weight; + + /// double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int k = 0; k < curl_dim; ++k) + { + mesh_coords_bar(k * mesh_ndof + j) += PointMat_bar(k, j); + } + } + } +} + +/** OLD UNUSED STUFF BELOW THIS LINE - SHOULD BE REMOVED */ + double TestLFIntegrator::GetElementEnergy(const FiniteElement &el, ElementTransformation &trans, const Vector &elfun) diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 0ace4e33..1e047341 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -677,6 +677,84 @@ inline void addSensitivityIntegrator( rev_sens.at("mesh_coords").AddDomainIntegrator(sens_integ); } +class DomainLFIntegrator final : public mfem::DomainLFIntegrator +{ +public: + DomainLFIntegrator(mfem::Coefficient &Q, double alpha = 1.0) + : mfem::DomainLFIntegrator(Q, 2, -2), F(Q), alpha(alpha) + { } + + inline void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) override + { + mfem::DomainLFIntegrator::AssembleRHSElementVect(el, trans, elvect); + if (alpha != 1.0) + { + elvect *= alpha; + } + } + +private: + /// coefficient for linear form + mfem::Coefficient &F; + /// scaling term if the linear form has a negative sign in the residual + const double alpha; + /// class that implements mesh sensitivities for + /// DomainLFIntegrator + friend class DomainLFIntegratorMeshRevSens; +}; + +class DomainLFIntegratorMeshRevSens final : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] adjoint - the adjoint to use when evaluating d(psi^T R)/dX + /// \param[in] integ - reference to primal integrator + DomainLFIntegratorMeshRevSens(mfem::GridFunction &adjoint, + mach::DomainLFIntegrator &integ) + : adjoint(adjoint), integ(integ) + { } + + /// \brief - assemble an element's contribution to d(psi^T f)/dX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - d(psi^T f)/dX for the element + /// \note the LinearForm that assembles this integrator's FiniteElementSpace + /// MUST be the mesh's nodal finite element space + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// the adjoint to use when evaluating d(psi^T R)/dX + mfem::GridFunction &adjoint; + /// reference to primal integrator + mach::DomainLFIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::Vector shape, shape_bar; + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector psi; +#endif +}; + +inline void addSensitivityIntegrator( + mach::DomainLFIntegrator &primal_integ, + std::map &fields, + std::map &rev_sens, + std::map &rev_scalar_sens, + std::map &fwd_sens, + std::map &fwd_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + rev_sens.emplace("mesh_coords", &mesh_fes); + rev_sens.at("mesh_coords") + .AddDomainIntegrator(new DomainLFIntegratorMeshRevSens( + fields.at("adjoint").gridFunc(), primal_integ)); +} + /** Not yet differentiated, class only needed if magnets are on the boundary and not normal to boundary class VectorFEBoundaryTangentLFIntegrator final diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index c63cf2ff..3b81eb92 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -499,6 +499,7 @@ void PDESolver::initialHook(const mfem::Vector &state) int inverted_elems = mesh().CheckElementOrientation(false); if (inverted_elems > 0) { + mesh().PrintVTU("inverted_mesh", mfem::VTKFormat::BINARY, true); throw MachException("Mesh contains inverted elements!\n"); } else diff --git a/test/regression/test_magnetostatic_box2d.cpp b/test/regression/test_magnetostatic_box2d.cpp index 70aa908a..f4aa7bd1 100644 --- a/test/regression/test_magnetostatic_box2d.cpp +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -90,8 +90,8 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", // define the target state solution error std::vector> target_error = { // nxy = 2, nxy = 4, nyx = 8, nyx = 16, nxy = 32 - {0.03446617612, 0.0, 0.0, 0.0, 0.0}, // p = 1 - {0.004470003778, 0.0, 0.0, 0.0, 0.0}, // p = 2 + {0.0306325207, 0.0, 0.0, 0.0, 0.0}, // p = 1 + {0.004603664996, 0.0, 0.0, 0.0, 0.0}, // p = 2 {0.0, 0.0, 0.0, 0.0, 0.0}, // p = 3 {0.0, 0.0, 0.0, 0.0, 0.0} // p = 4 }; diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index 9520a419..3865424f 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -1141,6 +1141,102 @@ TEST_CASE("VectorFEDomainLFCurlIntegratorMeshSens::AssembleRHSElementVect") } } +TEST_CASE("DomainLFIntegratorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + // q += x(i); + } + return q; + + // return 1.0; + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + // x_bar(i) += q_bar; + } + + // x_bar = 0.0; + }); + + // mfem::ConstantCoefficient model(2.0); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize adjoint; here we randomly perturb a constant state + GridFunction adjoint(&fes); + FunctionCoefficient pert(randState); + adjoint.ProjectCoefficient(pert); + + LinearForm res(&fes); + auto *integ = new mach::DomainLFIntegrator(model, -1.0); + res.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // initialize the vector that we use to perturb the mesh nodes + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // evaluate d(psi^T R)/dx and contract with v + LinearForm dfdx(&mesh_fes); + dfdx.AddDomainIntegrator( + new mach::DomainLFIntegratorMeshRevSens(adjoint, *integ)); + dfdx.Assemble(); + double dfdx_v = dfdx * v; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + GridFunction r(&fes); + x_pert.Add(delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Update(); + res.Assemble(); + double dfdx_v_fd = adjoint * res; + x_pert.Add(-2 * delta, v); + mesh.SetNodes(x_pert); + fes.Update(); + res.Update(); + res.Assemble(); + dfdx_v_fd -= adjoint * res; + dfdx_v_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + /** Not yet differentiated, class only needed if magnets are on the boundary and not normal to boundary TEST_CASE("VectorFEBoundaryTangentLFIntegratorMeshSens::AssembleRHSElementVect") From 956ab128ff718e242142f2a86a44549f75ac354f Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Fri, 16 Sep 2022 16:14:44 -0400 Subject: [PATCH 49/72] still a few more things to differentiate that I had forgotten about. Working on the DC loss right now, all the integrators seem to work, but when used as an Output there are slight issues. Need to investigate further. I suspect the issue is inside of FunctionalOutput --- src/physics/common_outputs.cpp | 27 +- src/physics/common_outputs.hpp | 51 ++- .../electromagnetics/electromag_integ.cpp | 79 +++- .../electromagnetics/electromag_integ.hpp | 41 ++ .../electromagnetics/electromag_outputs.cpp | 396 +++++++++++++++++- .../electromagnetics/electromag_outputs.hpp | 85 +++- .../magnetostatic_residual.cpp | 20 +- .../magnetostatic_residual.hpp | 3 + test/unit/CMakeLists.txt | 1 + test/unit/test_electromag_integ.cpp | 93 ++++ test/unit/test_mfem_common_integ.cpp | 1 - 11 files changed, 730 insertions(+), 67 deletions(-) diff --git a/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index d8abd5e1..f547774f 100644 --- a/src/physics/common_outputs.cpp +++ b/src/physics/common_outputs.cpp @@ -11,52 +11,39 @@ namespace mach { -double calcOutput(VolumeFunctional &output, const MachInputs &inputs) -{ - setInputs(output, inputs); - output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - return output.output.GetEnergy(output.scratch); -} - VolumeFunctional::VolumeFunctional( std::map &fields, const nlohmann::json &options) - : FunctionalOutput(fields.at("state").space(), fields) + : output(fields.at("state").space(), fields) { if (options.contains("attributes")) { auto attributes = options["attributes"].get>(); - addOutputDomainIntegrator(new VolumeIntegrator, attributes); + output.addOutputDomainIntegrator(new VolumeIntegrator, attributes); } else { - addOutputDomainIntegrator(new VolumeIntegrator); + output.addOutputDomainIntegrator(new VolumeIntegrator); } } -double calcOutput(MassFunctional &output, const MachInputs &inputs) -{ - setInputs(output, inputs); - output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - return output.output.GetEnergy(output.scratch); -} - MassFunctional::MassFunctional( std::map &fields, const nlohmann::json &components, const nlohmann::json &materials, const nlohmann::json &options) - : FunctionalOutput(fields.at("state").space(), fields), + : output(fields.at("state").space(), fields), rho(constructMaterialCoefficient("rho", components, materials)) { if (options.contains("attributes")) { auto attributes = options["attributes"].get>(); - addOutputDomainIntegrator(new VolumeIntegrator(rho.get()), attributes); + output.addOutputDomainIntegrator(new VolumeIntegrator(rho.get()), + attributes); } else { - addOutputDomainIntegrator(new VolumeIntegrator(rho.get())); + output.addOutputDomainIntegrator(new VolumeIntegrator(rho.get())); } } diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index 0681dba6..aceca3fe 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -12,56 +12,74 @@ namespace mach { -class VolumeFunctional : public FunctionalOutput +class VolumeFunctional final { public: friend inline int getSize(const VolumeFunctional &output) { - const auto &fun_output = dynamic_cast(output); - return getSize(fun_output); + return getSize(output.output); } friend void setOptions(VolumeFunctional &output, const nlohmann::json &options) { - auto &fun_output = dynamic_cast(output); - setOptions(fun_output, options); + setOptions(output.output, options); } friend void setInputs(VolumeFunctional &output, const MachInputs &inputs) { - auto &fun_output = dynamic_cast(output); - setInputs(fun_output, inputs); + setInputs(output.output, inputs); } - friend double calcOutput(VolumeFunctional &output, const MachInputs &inputs); + friend double calcOutput(VolumeFunctional &output, const MachInputs &inputs) + { + return calcOutput(output.output, inputs); + } + + friend double jacobianVectorProduct(VolumeFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) + { + return jacobianVectorProduct(output.output, wrt_dot, wrt); + } + + friend void vectorJacobianProduct(VolumeFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) + { + vectorJacobianProduct(output.output, out_bar, wrt, wrt_bar); + } VolumeFunctional(std::map &fields, const nlohmann::json &options); + +private: + FunctionalOutput output; }; -class MassFunctional : public FunctionalOutput +class MassFunctional final { public: friend inline int getSize(const MassFunctional &output) { - const auto &fun_output = dynamic_cast(output); - return getSize(fun_output); + return getSize(output.output); } friend void setOptions(MassFunctional &output, const nlohmann::json &options) { - auto &fun_output = dynamic_cast(output); - setOptions(fun_output, options); + setOptions(output.output, options); } friend void setInputs(MassFunctional &output, const MachInputs &inputs) { - auto &fun_output = dynamic_cast(output); - setInputs(fun_output, inputs); + setInputs(output.output, inputs); } - friend double calcOutput(MassFunctional &output, const MachInputs &inputs); + friend double calcOutput(MassFunctional &output, const MachInputs &inputs) + { + return calcOutput(output.output, inputs); + } MassFunctional(std::map &fields, const nlohmann::json &components, @@ -69,6 +87,7 @@ class MassFunctional : public FunctionalOutput const nlohmann::json &options); private: + FunctionalOutput output; /// Density std::unique_ptr rho; }; diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index ca4ad96f..b4b3fdd4 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3461,16 +3461,91 @@ double DCLossFunctionalIntegrator::GetElementEnergy( for (int i = 0; i < ir->GetNPoints(); i++) { const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - double w = ip.weight * trans.Weight(); + double trans_weight = trans.Weight(); + + double w = ip.weight * trans_weight; const double sigma_v = sigma.Eval(trans, ip); fun += w / sigma_v; } return fun; } +void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); + + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(mesh_trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (mesh_el.Space() == FunctionSpace::Pk) + { + return 2 * mesh_el.GetOrder() - 2; + } + else + { + return 2 * mesh_el.GetOrder(); + } + }(); + + ir = &IntRules.Get(mesh_el.GetGeomType(), order); + } + + auto &sigma = integ.sigma; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + mesh_trans.SetIntPoint(&ip); + + double trans_weight = mesh_trans.Weight(); + double w = ip.weight * trans_weight; + + const double sigma_v = sigma.Eval(mesh_trans, ip); + // fun += w / sigma_v; + + /// Start reverse pass... + double fun_bar = 1.0; + + /// fun += w / sigma_v; + double w_bar = fun_bar / sigma_v; + double sigma_v_bar = -fun_bar * w / pow(sigma_v, 2); + + /// const double sigma_v = sigma.Eval(mesh_trans, ip); + PointMat_bar = 0.0; + sigma.EvalRevDiff(sigma_v_bar, mesh_trans, ip, PointMat_bar); + + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; + + /// double trans_weight = mesh_trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setInputs(DCLossFunctionalDistributionIntegrator &integ, const MachInputs &inputs) { diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 6c69bef3..52d96137 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1104,8 +1104,49 @@ class DCLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator // double strand_radius; // double num_strands_in_hand; // double num_turns; + friend class DCLossFunctionalIntegratorMeshSens; }; +class DCLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + DCLossFunctionalIntegratorMeshSens(DCLossFunctionalIntegrator &integ) + : integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// reference to primal integrator + DCLossFunctionalIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addSensitivityIntegrator( + DCLossFunctionalIntegrator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new DCLossFunctionalIntegratorMeshSens(primal_integ)); +} + class DCLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator { public: diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 362d3434..04166d8f 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1,3 +1,4 @@ +#include #include #include "data_logging.hpp" @@ -6,6 +7,7 @@ #include "mfem.hpp" #include "coefficient.hpp" +#include "common_outputs.hpp" #include "mach_input.hpp" #include "electromag_outputs.hpp" #include "nlohmann/json.hpp" @@ -78,34 +80,412 @@ double calcOutput(DCLossFunctional &output, const MachInputs &inputs) /// rho = electrical resistivity, doesn't depend on any state /// so we just integrate with a dummy state vector - output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - double rho = output.output.GetEnergy(output.scratch); + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); double R = output.wire_length * rho / (strand_area * output.strands_in_hand); - double loss = pow(output.rms_current, 2) * R; - loss *= sqrt(2); + double loss = pow(output.rms_current, 2) * R * sqrt(2); double volume = calcOutput(output.volume, inputs); return loss / volume; } +double jacobianVectorProduct(DCLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) +{ + const MachInputs &inputs = *output.inputs; + if (wrt.rfind("wire_length", 0) == 0) + { + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + // double R = + // output.wire_length * rho / (strand_area * output.strands_in_hand); + double R_dot = rho / (strand_area * output.strands_in_hand) * wrt_dot(0); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + double loss_dot = pow(output.rms_current, 2) * sqrt(2) * R_dot; + + double volume = calcOutput(output.volume, inputs); + + // double dc_loss = loss / volume; + double dc_loss_dot = 1 / volume * loss_dot; + return dc_loss_dot; + } + else if (wrt.rfind("rms_current", 0) == 0) + { + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + double R = + output.wire_length * rho / (strand_area * output.strands_in_hand); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + double loss_dot = 2 * output.rms_current * R * sqrt(2) * wrt_dot(0); + + double volume = calcOutput(output.volume, inputs); + + // double dc_loss = loss / volume; + double dc_loss_dot = 1 / volume * loss_dot; + return dc_loss_dot; + } + else if (wrt.rfind("strand_radius", 0) == 0) + { + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + double strand_area_dot = M_PI * 2 * output.strand_radius * wrt_dot(0); + + // double R = + // output.wire_length * rho / (strand_area * output.strands_in_hand); + double R_dot = -output.wire_length * rho / + (pow(strand_area, 2) * output.strands_in_hand) * + strand_area_dot; + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + double loss_dot = pow(output.rms_current, 2) * sqrt(2) * R_dot; + + double volume = calcOutput(output.volume, inputs); + + // double dc_loss = loss / volume; + double dc_loss_dot = 1 / volume * loss_dot; + return dc_loss_dot; + } + else if (wrt.rfind("strands_in_hand", 0) == 0) + { + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + + // double R = + // output.wire_length * rho / (strand_area * output.strands_in_hand); + double R_dot = -output.wire_length * rho / + (strand_area * pow(output.strands_in_hand, 2)) * + wrt_dot(0); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + double loss_dot = pow(output.rms_current, 2) * sqrt(2) * R_dot; + + double volume = calcOutput(output.volume, inputs); + + // double dc_loss = loss / volume; + double dc_loss_dot = 1 / volume * loss_dot; + return dc_loss_dot; + } + else + { + return 0.0; + } +} + +void jacobianVectorProduct(DCLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt, + mfem::Vector &out_dot) +{ } + +double vectorJacobianProduct(DCLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt) +{ + const MachInputs &inputs = *output.inputs; + if (wrt.rfind("wire_length", 0) == 0) + { + /// rho = electrical resistivity, doesn't depend on any state + /// so we just integrate with a dummy state vector + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + // double R = output.wire_length * rho / (strand_area * + // output.strands_in_hand); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + + double volume = calcOutput(output.volume, inputs); + // double dc_loss = loss / volume; + + /// Start reverse pass... + double dc_loss_bar = out_bar(0); + + /// double dc_loss = loss / volume; + double loss_bar = dc_loss_bar / volume; + // double volume_bar = -dc_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = pow(output.rms_current, 2) * R * sqrt(2); + // double rms_current_bar = loss_bar * 2 * output.rms_current * R * + // sqrt(2); + double R_bar = loss_bar * pow(output.rms_current, 2) * sqrt(2); + + /// double R = output.wire_length * rho / (strand_area * + /// output.strands_in_hand); + double wire_length_bar = + R_bar * rho / (strand_area * output.strands_in_hand); + // double rho_bar = R_bar * output.wire_length / (strand_area * + // output.strands_in_hand); double strand_area_bar = -R_bar * + // output.wire_length * rho / (pow(strand_area,2) * + // output.strands_in_hand); double strands_in_hand_bar = -R_bar * + // output.wire_length * rho / (strand_area * pow(output.strands_in_hand, + // 2)); + + /// double strand_area = M_PI * pow(output.strand_radius, 2); + // double strand_radius_bar = strand_area_bar * M_PI * 2 * + // output.strand_radius; + + /// double rho = output.output.GetEnergy(output.scratch); + // rho does not depend on any of the inputs except mesh coords + + return wire_length_bar; + } + else if (wrt.rfind("rms_current", 0) == 0) + { + /// rho = electrical resistivity, doesn't depend on any state + /// so we just integrate with a dummy state vector + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + double R = + output.wire_length * rho / (strand_area * output.strands_in_hand); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + + double volume = calcOutput(output.volume, inputs); + // double dc_loss = loss / volume; + + /// Start reverse pass... + double dc_loss_bar = out_bar(0); + + /// double dc_loss = loss / volume; + double loss_bar = dc_loss_bar / volume; + // double volume_bar = -dc_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = pow(output.rms_current, 2) * R * sqrt(2); + double rms_current_bar = loss_bar * 2 * output.rms_current * R * sqrt(2); + // double R_bar = loss_bar * pow(output.rms_current, 2) * sqrt(2); + + /// double R = output.wire_length * rho / (strand_area * + /// output.strands_in_hand); + // double wire_length_bar = R_bar * rho / (strand_area * + // output.strands_in_hand); double rho_bar = R_bar * output.wire_length / + // (strand_area * output.strands_in_hand); double strand_area_bar = -R_bar + // * output.wire_length * rho / (pow(strand_area,2) * + // output.strands_in_hand); double strands_in_hand_bar = -R_bar * + // output.wire_length * rho / (strand_area * pow(output.strands_in_hand, + // 2)); + + /// double strand_area = M_PI * pow(output.strand_radius, 2); + // double strand_radius_bar = strand_area_bar * M_PI * 2 * + // output.strand_radius; + + /// double rho = output.output.GetEnergy(output.scratch); + // rho does not depend on any of the inputs except mesh coords + + return rms_current_bar; + } + else if (wrt.rfind("strand_radius", 0) == 0) + { + /// rho = electrical resistivity, doesn't depend on any state + /// so we just integrate with a dummy state vector + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + // double R = output.wire_length * rho / (strand_area * + // output.strands_in_hand); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + + double volume = calcOutput(output.volume, inputs); + // double dc_loss = loss / volume; + + /// Start reverse pass... + double dc_loss_bar = out_bar(0); + + /// double dc_loss = loss / volume; + double loss_bar = dc_loss_bar / volume; + // double volume_bar = -dc_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = pow(output.rms_current, 2) * R * sqrt(2); + // double rms_current_bar = loss_bar * 2 * output.rms_current * R * + // sqrt(2); + double R_bar = loss_bar * pow(output.rms_current, 2) * sqrt(2); + + /// double R = output.wire_length * rho / (strand_area * + /// output.strands_in_hand); + // double wire_length_bar = R_bar * rho / (strand_area * + // output.strands_in_hand); double rho_bar = R_bar * output.wire_length / + // (strand_area * output.strands_in_hand); + double strand_area_bar = -R_bar * output.wire_length * rho / + (pow(strand_area, 2) * output.strands_in_hand); + // double strands_in_hand_bar = -R_bar * output.wire_length * rho / + // (strand_area * pow(output.strands_in_hand, 2)); + + /// double strand_area = M_PI * pow(output.strand_radius, 2); + double strand_radius_bar = + strand_area_bar * M_PI * 2 * output.strand_radius; + + /// double rho = output.output.GetEnergy(output.scratch); + // rho does not depend on any of the inputs except mesh coords + + return strand_radius_bar; + } + else if (wrt.rfind("strands_in_hand", 0) == 0) + { + /// rho = electrical resistivity, doesn't depend on any state + /// so we just integrate with a dummy state vector + // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); + // double rho = output.output.GetEnergy(output.scratch); + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + // double R = output.wire_length * rho / (strand_area * + // output.strands_in_hand); + + // double loss = pow(output.rms_current, 2) * R * sqrt(2); + + double volume = calcOutput(output.volume, inputs); + // double dc_loss = loss / volume; + + /// Start reverse pass... + double dc_loss_bar = out_bar(0); + + /// double dc_loss = loss / volume; + double loss_bar = dc_loss_bar / volume; + // double volume_bar = -dc_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = pow(output.rms_current, 2) * R * sqrt(2); + // double rms_current_bar = loss_bar * 2 * output.rms_current * R * + // sqrt(2); + double R_bar = loss_bar * pow(output.rms_current, 2) * sqrt(2); + + /// double R = output.wire_length * rho / (strand_area * + /// output.strands_in_hand); + // double wire_length_bar = R_bar * rho / (strand_area * + // output.strands_in_hand); double rho_bar = R_bar * output.wire_length / + // (strand_area * output.strands_in_hand); double strand_area_bar = -R_bar + // * output.wire_length * rho / (pow(strand_area,2) * + // output.strands_in_hand); + double strands_in_hand_bar = + -R_bar * output.wire_length * rho / + (strand_area * pow(output.strands_in_hand, 2)); + + /// double strand_area = M_PI * pow(output.strand_radius, 2); + // double strand_radius_bar = strand_area_bar * M_PI * 2 * + // output.strand_radius; + + /// double rho = output.output.GetEnergy(output.scratch); + // rho does not depend on any of the inputs except mesh coords + + return strands_in_hand_bar; + } + else + { + return 0.0; + } +} + +void vectorJacobianProduct(DCLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + const MachInputs &inputs = *output.inputs; + if (wrt.rfind("mesh_coords", 0) == 0) + { + double rho = calcOutput(output.resistivity, inputs); + + double strand_area = M_PI * pow(output.strand_radius, 2); + double R = + output.wire_length * rho / (strand_area * output.strands_in_hand); + + double loss = pow(output.rms_current, 2) * R * sqrt(2); + + double volume = calcOutput(output.volume, inputs); + // double dc_loss = loss / volume; + + /// Start reverse pass... + double dc_loss_bar = out_bar(0); + + /// double dc_loss = loss / volume; + double loss_bar = dc_loss_bar / volume; + double volume_bar = -dc_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + mfem::Vector vol_bar_vec(&volume_bar, 1); + vectorJacobianProduct(output.volume, vol_bar_vec, wrt, wrt_bar); + + /// double loss = pow(output.rms_current, 2) * R * sqrt(2); + // double rms_current_bar = + // loss_bar * 2 * output.rms_current * R * sqrt(2); + double R_bar = loss_bar * pow(output.rms_current, 2) * sqrt(2); + + /// double R = + /// output.wire_length * rho / (strand_area * output.strands_in_hand); + // double wire_length_bar = + // R_bar * rho / (strand_area * output.strands_in_hand); + double rho_bar = + R_bar * output.wire_length / (strand_area * output.strands_in_hand); + // double strand_area_bar = -R_bar * output.wire_length * rho / + // (pow(strand_area, 2) * + // output.strands_in_hand); + // double strands_in_hand_bar = + // -R_bar * output.wire_length * rho / + // (strand_area * pow(output.strands_in_hand, 2)); + + /// double strand_area = M_PI * pow(output.strand_radius, 2); + // double strand_radius_bar = + // strand_area_bar * M_PI * 2 * output.strand_radius; + + /// double rho = calcOutput(output.resistivity, inputs); + mfem::Vector rho_bar_vec(&rho_bar, 1); + vectorJacobianProduct(output.resistivity, rho_bar_vec, wrt, wrt_bar); + } +} + DCLossFunctional::DCLossFunctional( std::map &fields, mfem::Coefficient &sigma, const nlohmann::json &options) - : FunctionalOutput(fields.at("state").space(), fields), volume(fields, options) + : resistivity(fields.at("state").space(), fields), volume(fields, options) { if (options.contains("attributes")) { auto attributes = options["attributes"].get>(); - addOutputDomainIntegrator(new DCLossFunctionalIntegrator(sigma), - attributes); + resistivity.addOutputDomainIntegrator( + new DCLossFunctionalIntegrator(sigma), attributes); } else { - addOutputDomainIntegrator(new DCLossFunctionalIntegrator(sigma)); + resistivity.addOutputDomainIntegrator( + new DCLossFunctionalIntegrator(sigma)); } } diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index c9090f97..0e697fbf 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -172,46 +172,111 @@ class TorqueFunctional final std::map &fields; }; -class DCLossFunctional final : private FunctionalOutput +// class ResistivityFunctional final +// { +// public: +// friend inline int getSize(const ResistivityFunctional &output) +// { +// return getSize(output.output); +// } + +// friend void setOptions(ResistivityFunctional &output, +// const nlohmann::json &options) +// { +// setOptions(output.output, options); +// } + +// friend void setInputs(ResistivityFunctional &output, const MachInputs +// &inputs) +// { +// setInputs(output.output, inputs); +// } +// friend double calcOutput(ResistivityFunctional &output, const MachInputs +// &inputs); + +// friend double jacobianVectorProduct(ResistivityFunctional &output, +// const mfem::Vector &wrt_dot, +// const std::string &wrt); + +// friend void jacobianVectorProduct(ResistivityFunctional &output, +// const mfem::Vector &wrt_dot, +// const std::string &wrt, +// mfem::Vector &out_dot); + +// friend double vectorJacobianProduct(ResistivityFunctional &output, +// const mfem::Vector &out_bar, +// const std::string &wrt); + +// friend void vectorJacobianProduct(ResistivityFunctional &output, +// const mfem::Vector &out_bar, +// const std::string &wrt, +// mfem::Vector &wrt_bar); + +// ResistivityFunctional(); + +// private: +// FunctionalOutput output; + +// }; + +class DCLossFunctional final { public: - friend inline int getSize(const DCLossFunctional &output) - { - const auto &fun_output = dynamic_cast(output); - return getSize(fun_output); - } + friend inline int getSize(const DCLossFunctional &output) { return 1; } friend void setOptions(DCLossFunctional &output, const nlohmann::json &options) { - auto &fun_output = dynamic_cast(output); - setOptions(fun_output, options); + setOptions(output.resistivity, options); + setOptions(output.volume, options); } friend void setInputs(DCLossFunctional &output, const MachInputs &inputs) { + output.inputs = &inputs; setValueFromInputs(inputs, "wire_length", output.wire_length); setValueFromInputs(inputs, "rms_current", output.rms_current); setValueFromInputs(inputs, "strand_radius", output.strand_radius); setValueFromInputs(inputs, "strands_in_hand", output.strands_in_hand); - auto &fun_output = dynamic_cast(output); - setInputs(fun_output, inputs); + setInputs(output.resistivity, inputs); + setInputs(output.volume, inputs); } friend double calcOutput(DCLossFunctional &output, const MachInputs &inputs); + friend double jacobianVectorProduct(DCLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); + + friend void jacobianVectorProduct(DCLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt, + mfem::Vector &out_dot); + + friend double vectorJacobianProduct(DCLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt); + + friend void vectorJacobianProduct(DCLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); + DCLossFunctional(std::map &fields, mfem::Coefficient &sigma, const nlohmann::json &options); private: + FunctionalOutput resistivity; VolumeFunctional volume; double wire_length = 1.0; double rms_current = 1.0; double strand_radius = 1.0; double strands_in_hand = 1.0; + + MachInputs const *inputs = nullptr; }; class ACLossFunctional final diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index b0deae77..55304f2c 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -145,13 +145,13 @@ void jacobianVectorProduct(MagnetostaticResidual &residual, MachInputs inputs{{wrt, 1.0}}; setInputs(*residual.current_coeff, inputs); - mfem::Vector scratch(res_dot.Size()); - scratch = 0.0; - addLoad(*residual.load, scratch); + residual.scratch.SetSize(res_dot.Size()); + residual.scratch = 0.0; + addLoad(*residual.load, residual.scratch); residual.current_coeff->resetCurrentDensityFromCache(); - scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); - res_dot.Add(wrt_dot(0), scratch); + residual.scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); + res_dot.Add(wrt_dot(0), residual.scratch); return; } jacobianVectorProduct(residual.res, wrt_dot, wrt, res_dot); @@ -171,13 +171,13 @@ double vectorJacobianProduct(MagnetostaticResidual &residual, MachInputs inputs{{wrt, 1.0}}; setInputs(*residual.current_coeff, inputs); - mfem::Vector scratch(res_bar.Size()); - scratch = 0.0; - addLoad(*residual.load, scratch); + residual.scratch.SetSize(res_bar.Size()); + residual.scratch = 0.0; + addLoad(*residual.load, residual.scratch); residual.current_coeff->resetCurrentDensityFromCache(); - scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); - return scratch * res_bar; + residual.scratch.SetSubVector(residual.res.getEssentialDofs(), 0.0); + return residual.scratch * res_bar; } auto wrt_bar = vectorJacobianProduct(residual.res, res_bar, wrt); wrt_bar += vectorJacobianProduct(*residual.load, res_bar, wrt); diff --git a/src/physics/electromagnetics/magnetostatic_residual.hpp b/src/physics/electromagnetics/magnetostatic_residual.hpp index 571fa56b..b96e8648 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.hpp +++ b/src/physics/electromagnetics/magnetostatic_residual.hpp @@ -90,6 +90,9 @@ class MagnetostaticResidual final /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; + + /// Work vector + mfem::Vector scratch; }; } // namespace mach diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index d68a25ab..9d10be61 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -56,6 +56,7 @@ set(FLUID_MPI_TEST_SRCS # group EM MPI tests set(EM_MPI_TEST_SRCS test_electromag_integ + test_electromag_outputs test_irrotational_projector test_div_free_projector test_magnetostatic_solver diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index b11a5c40..06b93869 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -2081,6 +2081,99 @@ TEST_CASE("calcMagneticEnergyDoubleDot") } } +TEST_CASE("DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + // q += sqrt(x(i)); + // q += pow(x(i), 5); + } + return q; + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); + // x_bar(i) += q_bar * 5 * pow(x(i), 4); + } + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + mesh.SetCurvature(p); + + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::DCLossFunctionalIntegrator(model); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::DCLossFunctionalIntegratorMeshSens(*integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("ForceIntegrator3::GetElementEnergy") { using namespace mfem; diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index 3865424f..eaf1e64b 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -1215,7 +1215,6 @@ TEST_CASE("DomainLFIntegratorMeshSens::AssembleRHSElementVect") // now compute the finite-difference approximation... GridFunction x_pert(x_nodes); - GridFunction r(&fes); x_pert.Add(delta, v); mesh.SetNodes(x_pert); fes.Update(); From fb91b843da2141daf3d7e734822e91cc90e2d6b7 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Fri, 16 Sep 2022 16:19:46 -0400 Subject: [PATCH 50/72] adding electromag output test that shows the derivative issue, with a 4th order finite difference check as well as the second order check to give confidence to the fact that the finite difference is correct --- test/unit/test_electromag_outputs.cpp | 361 ++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 test/unit/test_electromag_outputs.cpp diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp new file mode 100644 index 00000000..d9604414 --- /dev/null +++ b/test/unit/test_electromag_outputs.cpp @@ -0,0 +1,361 @@ +#include + +#include "catch.hpp" +#include "finite_element_state.hpp" +#include "functional_output.hpp" +#include "mfem.hpp" + +#include "electromag_outputs.hpp" + +#include "electromag_test_data.hpp" + +// TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") +// { +// using namespace mfem; +// using namespace electromag_data; + +// double delta = 1e-5; + +// // generate a 6 element mesh +// int num_edge = 1; +// auto smesh = Mesh::MakeCartesian2D(num_edge, +// num_edge, +// Element::TRIANGLE); +// mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + +// mesh.EnsureNodes(); +// const auto dim = mesh.SpaceDimension(); + +// mfem::FunctionCoefficient model( +// [](const mfem::Vector &x) +// { +// // double q = 0; +// // for (int i = 0; i < x.Size(); ++i) +// // { + +// // // q += pow(x(i), 2); +// // // q += sqrt(x(i)); +// // // q += pow(x(i), 5); +// // } +// // return q; +// return exp(-pow(x(0),2)); +// }, +// [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) +// { +// // for (int i = 0; i < x.Size(); ++i) +// // { +// // x_bar(i) += q_bar * 2 * x(i); +// // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); +// // // x_bar(i) += q_bar * 5 * pow(x(i), 4); +// // } +// x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + +// }); + +// for (int p = 1; p <= 4; ++p) +// { +// DYNAMIC_SECTION( "...for degree p = " << p ) +// { + +// mfem::H1_FECollection fec(p, dim); +// mfem::ParFiniteElementSpace fes(&mesh, &fec); + +// std::map fields; +// fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + +// auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); +// auto *mesh_fespace = mesh_gf.ParFESpace(); +// /// create new state vector copying the mesh's fe space +// fields.emplace("mesh_coords", +// mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); +// auto &mesh_coords = fields.at("mesh_coords"); +// /// set the values of the new GF to those of the mesh's old nodes +// mesh_coords.gridFunc() = mesh_gf; +// /// tell the mesh to use this GF for its Nodes +// /// (and that it doesn't own it) +// mesh.NewNodes(mesh_coords.gridFunc(), false); + +// mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); +// mesh_coords.setTrueVec(mesh_coords_tv); + +// mach::DCLossFunctional fun(fields, model, {}); +// mach::MachInputs inputs{ +// {"mesh_coords", mesh_coords_tv} +// }; + +// double dc_loss = calcOutput(fun, inputs); +// std::cout << "dc_loss: " << dc_loss << "\n"; + +// // initialize the vector that we use to perturb the mesh nodes +// VectorFunctionCoefficient v_pert(dim, randVectorState); + +// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); +// v_tv = 0.0; +// v_tv(0) = 1.0; +// // mesh_coords.project(v_pert, v_tv); + +// // mesh_coords.distributeSharedDofs(mesh_coords_tv); + +// mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); +// wrt_bar = 0.0; +// // evaluate d(psi^T R)/dx and contract with v +// mfem::Vector adjoint_tv(1); +// adjoint_tv(0) = 1.0; +// setInputs(fun, inputs); +// vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); +// double dfdx_v = wrt_bar * v_tv; + +// // now compute the finite-difference approximation... +// mesh_coords_tv.Add(delta, v_tv); +// double dfdx_v_fd_p = calcOutput(fun, inputs); +// std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + +// mesh_coords_tv.Add(-2 * delta, v_tv); +// // mesh_coords_tv.Add(-delta, v_tv); +// double dfdx_v_fd_m = calcOutput(fun, inputs); +// std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + +// double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + +// // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes +// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; +// REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); +// } +// } +// } + +// TEST_CASE("Resistivity::vectorJacobianProduct wrt mesh_coords") +// { +// using namespace mfem; +// using namespace electromag_data; + +// double delta = 1e-5; + +// // generate a 6 element mesh +// int num_edge = 1; +// auto smesh = Mesh::MakeCartesian2D(num_edge, +// num_edge, +// Element::TRIANGLE); +// mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + +// mesh.EnsureNodes(); +// const auto dim = mesh.SpaceDimension(); + +// mfem::FunctionCoefficient model( +// [](const mfem::Vector &x) +// { +// // double q = 0; +// // for (int i = 0; i < x.Size(); ++i) +// // { + +// // // q += pow(x(i), 2); +// // // q += sqrt(x(i)); +// // // q += pow(x(i), 5); +// // } +// // return q; +// return exp(-pow(x(0),2)); +// }, +// [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) +// { +// // for (int i = 0; i < x.Size(); ++i) +// // { +// // x_bar(i) += q_bar * 2 * x(i); +// // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); +// // // x_bar(i) += q_bar * 5 * pow(x(i), 4); +// // } +// x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + +// }); + +// for (int p = 1; p <= 4; ++p) +// { +// DYNAMIC_SECTION( "...for degree p = " << p ) +// { + +// mfem::H1_FECollection fec(p, dim); +// mfem::ParFiniteElementSpace fes(&mesh, &fec); + +// std::map fields; +// fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + +// auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); +// auto *mesh_fespace = mesh_gf.ParFESpace(); +// /// create new state vector copying the mesh's fe space +// fields.emplace("mesh_coords", +// mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); +// auto &mesh_coords = fields.at("mesh_coords"); +// /// set the values of the new GF to those of the mesh's old nodes +// mesh_coords.gridFunc() = mesh_gf; +// /// tell the mesh to use this GF for its Nodes +// /// (and that it doesn't own it) +// mesh.NewNodes(mesh_coords.gridFunc(), false); + +// mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); +// mesh_coords.setTrueVec(mesh_coords_tv); + +// mach::FunctionalOutput fun(fields.at("state").space(), fields); +// fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); + +// mach::MachInputs inputs{ +// {"mesh_coords", mesh_coords_tv} +// }; + +// double dc_loss = calcOutput(fun, inputs); +// std::cout << "dc_loss: " << dc_loss << "\n"; + +// // initialize the vector that we use to perturb the mesh nodes +// VectorFunctionCoefficient v_pert(dim, randVectorState); + +// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); +// v_tv = 0.0; +// v_tv(0) = 1.0; +// // mesh_coords.project(v_pert, v_tv); + +// // mesh_coords.distributeSharedDofs(mesh_coords_tv); + +// mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); +// wrt_bar = 0.0; +// // evaluate d(psi^T R)/dx and contract with v +// mfem::Vector adjoint_tv(1); +// adjoint_tv(0) = 1.0; +// setInputs(fun, inputs); +// vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); +// double dfdx_v = wrt_bar * v_tv; + +// // now compute the finite-difference approximation... +// mesh_coords_tv.Add(delta, v_tv); +// double dfdx_v_fd_p = calcOutput(fun, inputs); +// std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + +// mesh_coords_tv.Add(-2 * delta, v_tv); +// // mesh_coords_tv.Add(-delta, v_tv); +// double dfdx_v_fd_m = calcOutput(fun, inputs); +// std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + +// double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + +// // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes +// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; +// REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); +// } +// } +// } + +TEST_CASE("Resistivity::jacobianVectorProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-6; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + // double q = 0; + // for (int i = 0; i < x.Size(); ++i) + // { + + // // q += pow(x(i), 2); + // // q += sqrt(x(i)); + // // q += pow(x(i), 5); + // } + // return q; + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + // for (int i = 0; i < x.Size(); ++i) + // { + // x_bar(i) += q_bar * 2 * x(i); + // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); + // // x_bar(i) += q_bar * 5 * pow(x(i), 4); + // } + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::FunctionalOutput fun(fields.at("state").space(), fields); + fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); + + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + double dc_loss = calcOutput(fun, inputs); + std::cout << "dc_loss: " << dc_loss << "\n"; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + v_tv = 0.0; + v_tv(0) = 1.0; + // mesh_coords.project(v_pert, v_tv); + + // mesh_coords.distributeSharedDofs(mesh_coords_tv); + + // evaluate d(psi^T R)/dx and contract with v + setInputs(fun, inputs); + double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(2*delta, v_tv); + double dfdx_v_fd_p_2 = calcOutput(fun, inputs); + + mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m_2 = calcOutput(fun, inputs); + + double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + double dfdx_v_fd_4 = (-dfdx_v_fd_p_2 + 8*dfdx_v_fd_p - 8*dfdx_v_fd_m + dfdx_v_fd_m_2) / (12 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << " dfdx_v_fd_4: " << dfdx_v_fd_4 << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} \ No newline at end of file From 4c07b2b329d8ccddff682d7ebbbad8eb1e276327 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Sun, 18 Sep 2022 20:37:02 -0400 Subject: [PATCH 51/72] fixed DCLossFunctional derivatives. Issue was that mesh sens integrator was using a different integration rule than the primal integrator. In testing the derivatives on the motor, realized that I needed to allow sensitivity integrators to be restricted to the same attributes that the primal integrators are restricted to. Make that change by renaming addSensitivityIntegrator to add*SensitivityIntegrator for Domain, InteriorFace, and Bdr integ types. Added pointer arg for optional attributes to restrict sens integrator to. Need to make the same change for residual sensitivity integrators. --- mach/mach_output.py | 5 + src/common/functional_output.hpp | 18 +- src/common/mach_output.hpp | 4 +- .../electromagnetics/electromag_integ.cpp | 29 +- .../electromagnetics/electromag_integ.hpp | 37 +- .../electromagnetics/electromag_outputs.cpp | 65 +-- src/physics/mach_integrator.hpp | 45 +- src/physics/mfem_common_integ.cpp | 26 +- src/physics/mfem_common_integ.hpp | 33 +- test/unit/test_common_outputs.cpp | 189 +++++++++ test/unit/test_electromag_integ.cpp | 4 +- test/unit/test_electromag_outputs.cpp | 397 +++++++++++------- test/unit/test_mfem_common_integ.cpp | 4 +- 13 files changed, 615 insertions(+), 241 deletions(-) diff --git a/mach/mach_output.py b/mach/mach_output.py index ef446804..757533fa 100644 --- a/mach/mach_output.py +++ b/mach/mach_output.py @@ -125,12 +125,17 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): input_dict = dict(zip(inputs.keys(), inputs.values())) input_dict.update(self.vectors) + for input in inputs: + print(f"d_inputs[{input}]: {d_inputs[input]}") + try: if mode == 'fwd': if func in d_outputs: for input in inputs: if input in d_inputs: func_dot = np.zeros_like(d_outputs[func]) + + print(f"wrt_dot for input {input}: {d_inputs[input]}") solver.outputJacobianVectorProduct(of=func, inputs=input_dict, wrt_dot=d_inputs[input], diff --git a/src/common/functional_output.hpp b/src/common/functional_output.hpp index 0e82f7f5..6be3a2ac 100644 --- a/src/common/functional_output.hpp +++ b/src/common/functional_output.hpp @@ -142,8 +142,8 @@ void FunctionalOutput::addOutputDomainIntegrator(T *integrator) { integs.emplace_back(*integrator); output.AddDomainIntegrator(integrator); - addSensitivityIntegrator( - *integrator, *func_fields, output_sens, output_scalar_sens); + addDomainSensitivityIntegrator( + *integrator, *func_fields, output_sens, output_scalar_sens, nullptr); } template @@ -158,8 +158,8 @@ void FunctionalOutput::addOutputDomainIntegrator( auto &marker = domain_markers.emplace_back(mesh_attr_size); attrVecToArray(attr_marker, marker); output.AddDomainIntegrator(integrator, marker); - addSensitivityIntegrator( - *integrator, *func_fields, output_sens, output_scalar_sens); + addDomainSensitivityIntegrator( + *integrator, *func_fields, output_sens, output_scalar_sens, &marker); } template @@ -167,7 +167,7 @@ void FunctionalOutput::addOutputInteriorFaceIntegrator(T *integrator) { integs.emplace_back(*integrator); output.AddInteriorFaceIntegrator(integrator); - addSensitivityIntegrator( + addInteriorFaceSensitivityIntegrator( *integrator, *func_fields, output_sens, output_scalar_sens); } @@ -176,8 +176,8 @@ void FunctionalOutput::addOutputBdrFaceIntegrator(T *integrator) { integs.emplace_back(*integrator); output.AddBdrFaceIntegrator(integrator); - addSensitivityIntegrator( - *integrator, *func_fields, output_sens, output_scalar_sens); + addBdrSensitivityIntegrator( + *integrator, *func_fields, output_sens, output_scalar_sens, nullptr); } template @@ -192,8 +192,8 @@ void FunctionalOutput::addOutputBdrFaceIntegrator( attrVecToArray(bdr_attr_marker, marker); output.AddBdrFaceIntegrator(integrator, marker); - addSensitivityIntegrator( - *integrator, *func_fields, output_sens, output_scalar_sens); + addBdrSensitivityIntegrator( + *integrator, *func_fields, output_sens, output_scalar_sens, &marker); } } // namespace mach diff --git a/src/common/mach_output.hpp b/src/common/mach_output.hpp index 4fc60664..ddb2fa12 100644 --- a/src/common/mach_output.hpp +++ b/src/common/mach_output.hpp @@ -83,7 +83,7 @@ class MachOutput final const MachInputs &inputs, mfem::Vector &out_vec); - /// Compute the output's sensitivity to a scalar and contract it with + /// Compute a scalar output's sensitivity to @a wrt and contract it with /// wrt_dot /// \param[inout] output - the output whose sensitivity we want /// \param[in] wrt_dot - the "wrt"-sized vector to contract with the @@ -95,7 +95,7 @@ class MachOutput final const mfem::Vector &wrt_dot, const std::string &wrt); - /// Compute the output's sensitivity to a vector and contract it with + /// Compute a vector output's sensitivity to @a wrt and contract it with /// wrt_dot /// \param[inout] output - the output whose sensitivity we want /// \param[in] wrt_dot - the "wrt"-sized vector to contract with the diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index b4b3fdd4..f5b03ed9 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3477,30 +3477,34 @@ void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( mfem::ElementTransformation &mesh_trans, mfem::Vector &mesh_coords_bar) { + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + const int mesh_ndof = mesh_el.GetDof(); const int space_dim = mesh_trans.GetSpaceDim(); PointMat_bar.SetSize(space_dim, mesh_ndof); // cast the ElementTransformation - auto &isotrans = dynamic_cast(mesh_trans); + auto &isotrans = dynamic_cast(trans); const IntegrationRule *ir = IntRule; if (ir == nullptr) { int order = [&]() { - if (mesh_el.Space() == FunctionSpace::Pk) + if (el.Space() == FunctionSpace::Pk) { - return 2 * mesh_el.GetOrder() - 2; + return 2 * el.GetOrder() - 2; } else { - return 2 * mesh_el.GetOrder(); + return 2 * el.GetOrder(); } }(); - ir = &IntRules.Get(mesh_el.GetGeomType(), order); + ir = &IntRules.Get(el.GetGeomType(), order); } auto &sigma = integ.sigma; @@ -3510,12 +3514,12 @@ void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( for (int i = 0; i < ir->GetNPoints(); i++) { const auto &ip = ir->IntPoint(i); - mesh_trans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - double trans_weight = mesh_trans.Weight(); + double trans_weight = trans.Weight(); double w = ip.weight * trans_weight; - const double sigma_v = sigma.Eval(mesh_trans, ip); + const double sigma_v = sigma.Eval(trans, ip); // fun += w / sigma_v; /// Start reverse pass... @@ -3525,14 +3529,17 @@ void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( double w_bar = fun_bar / sigma_v; double sigma_v_bar = -fun_bar * w / pow(sigma_v, 2); - /// const double sigma_v = sigma.Eval(mesh_trans, ip); + // std::cout << "sigma_v_bar: " << sigma_v_bar << "\n"; + // std::cout << "fun_bar: " << fun_bar << " w: " << w << " sigma_v: " << sigma_v << "\n"; + + /// const double sigma_v = sigma.Eval(trans, ip); PointMat_bar = 0.0; - sigma.EvalRevDiff(sigma_v_bar, mesh_trans, ip, PointMat_bar); + sigma.EvalRevDiff(sigma_v_bar, trans, ip, PointMat_bar); /// double w = ip.weight * trans_weight; double trans_weight_bar = w_bar * ip.weight; - /// double trans_weight = mesh_trans.Weight(); + /// double trans_weight = trans.Weight(); isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); /// code to insert PointMat_bar into mesh_coords_bar; diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 52d96137..8fc0efaf 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1110,22 +1110,26 @@ class DCLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator class DCLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator { public: + /// \param[in] state - the state grid function /// \param[in] integ - reference to primal integrator that holds inputs for /// integrator - DCLossFunctionalIntegratorMeshSens(DCLossFunctionalIntegrator &integ) - : integ(integ) + DCLossFunctionalIntegratorMeshSens(mfem::GridFunction &state, + DCLossFunctionalIntegrator &integ) + : state(state), integ(integ) { } /// \brief - assemble an element's contribution to dJdX - /// \param[in] el - the finite element that describes the mesh element - /// \param[in] trans - the transformation between reference and physical + /// \param[in] mesh_el - the finite element that describes the mesh element + /// \param[in] mesh_trans - the transformation between reference and physical /// space /// \param[out] mesh_coords_bar - dJdX for the element - void AssembleRHSElementVect(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, + void AssembleRHSElementVect(const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, mfem::Vector &mesh_coords_bar) override; private: + /// State GridFunction, needed to get integration order for each element + mfem::GridFunction &state; /// reference to primal integrator DCLossFunctionalIntegrator &integ; @@ -1134,17 +1138,28 @@ class DCLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( DCLossFunctionalIntegrator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator( - new DCLossFunctionalIntegratorMeshSens(primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), *attr_marker); + } } class DCLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 04166d8f..58dceb21 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1,16 +1,18 @@ #include #include -#include "data_logging.hpp" -#include "electromag_integ.hpp" -#include "mach_integrator.hpp" #include "mfem.hpp" +#include "nlohmann/json.hpp" #include "coefficient.hpp" #include "common_outputs.hpp" +#include "data_logging.hpp" +#include "electromag_integ.hpp" +#include "functional_output.hpp" #include "mach_input.hpp" +// #include "mach_integrator.hpp" + #include "electromag_outputs.hpp" -#include "nlohmann/json.hpp" namespace mach { @@ -78,10 +80,6 @@ double calcOutput(DCLossFunctional &output, const MachInputs &inputs) { setInputs(output, inputs); - /// rho = electrical resistivity, doesn't depend on any state - /// so we just integrate with a dummy state vector - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -100,8 +98,8 @@ double jacobianVectorProduct(DCLossFunctional &output, const MachInputs &inputs = *output.inputs; if (wrt.rfind("wire_length", 0) == 0) { - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); + std::cout << "wire_length_dot = " << wrt_dot(0) << "\n"; + double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -116,12 +114,11 @@ double jacobianVectorProduct(DCLossFunctional &output, // double dc_loss = loss / volume; double dc_loss_dot = 1 / volume * loss_dot; + std::cout << "dc_loss_dot = " << dc_loss_dot << "\n"; return dc_loss_dot; } else if (wrt.rfind("rms_current", 0) == 0) { - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -139,8 +136,6 @@ double jacobianVectorProduct(DCLossFunctional &output, } else if (wrt.rfind("strand_radius", 0) == 0) { - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -163,8 +158,6 @@ double jacobianVectorProduct(DCLossFunctional &output, } else if (wrt.rfind("strands_in_hand", 0) == 0) { - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -184,6 +177,30 @@ double jacobianVectorProduct(DCLossFunctional &output, double dc_loss_dot = 1 / volume * loss_dot; return dc_loss_dot; } + else if (wrt.rfind("mesh_coords", 0) == 0) + { + double rho = calcOutput(output.resistivity, inputs); + double rho_dot = jacobianVectorProduct(output.resistivity, wrt_dot, wrt); + + double strand_area = M_PI * pow(output.strand_radius, 2); + + double R = + output.wire_length * rho / (strand_area * output.strands_in_hand); + double R_dot = + output.wire_length / (strand_area * output.strands_in_hand) * rho_dot; + + double loss = pow(output.rms_current, 2) * R * sqrt(2); + double loss_dot = pow(output.rms_current, 2) * sqrt(2) * R_dot; + + double volume = calcOutput(output.volume, inputs); + double volume_dot = jacobianVectorProduct(output.volume, wrt_dot, wrt); + + // double dc_loss = loss / volume; + double dc_loss_dot = + loss_dot / volume - loss / pow(volume, 2) * volume_dot; + + return dc_loss_dot; + } else { return 0.0; @@ -203,10 +220,6 @@ double vectorJacobianProduct(DCLossFunctional &output, const MachInputs &inputs = *output.inputs; if (wrt.rfind("wire_length", 0) == 0) { - /// rho = electrical resistivity, doesn't depend on any state - /// so we just integrate with a dummy state vector - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -255,10 +268,6 @@ double vectorJacobianProduct(DCLossFunctional &output, } else if (wrt.rfind("rms_current", 0) == 0) { - /// rho = electrical resistivity, doesn't depend on any state - /// so we just integrate with a dummy state vector - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -305,10 +314,6 @@ double vectorJacobianProduct(DCLossFunctional &output, } else if (wrt.rfind("strand_radius", 0) == 0) { - /// rho = electrical resistivity, doesn't depend on any state - /// so we just integrate with a dummy state vector - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); @@ -356,10 +361,6 @@ double vectorJacobianProduct(DCLossFunctional &output, } else if (wrt.rfind("strands_in_hand", 0) == 0) { - /// rho = electrical resistivity, doesn't depend on any state - /// so we just integrate with a dummy state vector - // output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); - // double rho = output.output.GetEnergy(output.scratch); double rho = calcOutput(output.resistivity, inputs); double strand_area = M_PI * pow(output.strand_radius, 2); diff --git a/src/physics/mach_integrator.hpp b/src/physics/mach_integrator.hpp index 1ac10c13..5b11701c 100644 --- a/src/physics/mach_integrator.hpp +++ b/src/physics/mach_integrator.hpp @@ -152,12 +152,53 @@ inline void addSensitivityIntegrator( /// output partial derivatives wrt to fields /// \param[inout] output_scalar_sens - map of nonlinear forms that will /// assemble the output partial derivatives wrt to scalars +/// \param[in] attr_marker - optional list of element attributes the sensitivity +/// integrators should be used on template -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( + T &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker = nullptr) +{ } + +/// Function meant to be overloaded to allow output sensitivity integrators +/// to be associated with the forward version of the integrator +/// \param[in] primal_integ - integrator used in forward evaluation +/// \param[in] fields - map of fields solver depends on +/// \param[inout] output_sens - map of linear forms that will assemble the +/// output partial derivatives wrt to fields +/// \param[inout] output_scalar_sens - map of nonlinear forms that will +/// assemble the output partial derivatives wrt to scalars +/// \param[in] attr_marker - optional list of element attributes the sensitivity +/// integrators should be used on +template +inline void addInteriorFaceSensitivityIntegrator( + T &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker) +{ } + +/// Function meant to be overloaded to allow output sensitivity integrators +/// to be associated with the forward version of the integrator +/// \param[in] primal_integ - integrator used in forward evaluation +/// \param[in] fields - map of fields solver depends on +/// \param[inout] output_sens - map of linear forms that will assemble the +/// output partial derivatives wrt to fields +/// \param[inout] output_scalar_sens - map of nonlinear forms that will +/// assemble the output partial derivatives wrt to scalars +/// \param[in] attr_marker - optional list of element attributes the sensitivity +/// integrators should be used on +template +inline void addBdrSensitivityIntegrator( T &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { } } // namespace mach diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index 2a6972a6..f5694ef9 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -51,30 +51,34 @@ void VolumeIntegratorMeshSens::AssembleRHSElementVect( ElementTransformation &mesh_trans, Vector &mesh_coords_bar) { + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + const int mesh_ndof = mesh_el.GetDof(); const int space_dim = mesh_trans.GetSpaceDim(); PointMat_bar.SetSize(space_dim, mesh_ndof); // cast the ElementTransformation - auto &isotrans = dynamic_cast(mesh_trans); + auto &isotrans = dynamic_cast(trans); const IntegrationRule *ir = IntRule; if (ir == nullptr) { int order = [&]() { - if (mesh_el.Space() == FunctionSpace::Pk) + if (el.Space() == FunctionSpace::Pk) { - return 2 * mesh_el.GetOrder() - 2; + return 2 * el.GetOrder() - 2; } else { - return 2 * mesh_el.GetOrder(); + return 2 * el.GetOrder(); } }(); - ir = &IntRules.Get(mesh_el.GetGeomType(), order); + ir = &IntRules.Get(el.GetGeomType(), order); } auto *rho = integ.rho; @@ -84,9 +88,9 @@ void VolumeIntegratorMeshSens::AssembleRHSElementVect( for (int i = 0; i < ir->GetNPoints(); i++) { const auto &ip = ir->IntPoint(i); - mesh_trans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - double trans_weight = mesh_trans.Weight(); + double trans_weight = trans.Weight(); double w = ip.weight * trans_weight; @@ -99,14 +103,14 @@ void VolumeIntegratorMeshSens::AssembleRHSElementVect( PointMat_bar = 0.0; if (rho != nullptr) { - double s = rho->Eval(mesh_trans, ip); + double s = rho->Eval(trans, ip); /// val = w * s; double s_bar = val_bar * w; w_bar += val_bar * s; - /// double s = rho->Eval(mesh_trans, ip); - rho->EvalRevDiff(s_bar, mesh_trans, ip, PointMat_bar); + /// double s = rho->Eval(trans, ip); + rho->EvalRevDiff(s_bar, trans, ip, PointMat_bar); } else { @@ -117,7 +121,7 @@ void VolumeIntegratorMeshSens::AssembleRHSElementVect( /// double w = ip.weight * trans_weight; double trans_weight_bar = w_bar * ip.weight; - /// double trans_weight = mesh_trans.Weight(); + /// double trans_weight = trans.Weight(); isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); /// code to insert PointMat_bar into mesh_coords_bar; diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 1e047341..943fb1fb 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -37,20 +37,25 @@ class VolumeIntegrator : public mfem::NonlinearFormIntegrator class VolumeIntegratorMeshSens : public mfem::LinearFormIntegrator { public: + /// \param[in] state - the state grid function /// \param[in] integ - reference to primal integrator that holds inputs for /// integrator - VolumeIntegratorMeshSens(VolumeIntegrator &integ) : integ(integ) { } + VolumeIntegratorMeshSens(mfem::GridFunction &state, VolumeIntegrator &integ) + : state(state), integ(integ) + { } /// \brief - assemble an element's contribution to dJdX - /// \param[in] el - the finite element that describes the mesh element - /// \param[in] trans - the transformation between reference and physical + /// \param[in] mesh_el - the finite element that describes the mesh element + /// \param[in] mesh_trans - the transformation between reference and physical /// space /// \param[out] mesh_coords_bar - dJdX for the element - void AssembleRHSElementVect(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, + void AssembleRHSElementVect(const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, mfem::Vector &mesh_coords_bar) override; private: + /// State GridFunction, needed to get integration order for each element + mfem::GridFunction &state; /// reference to primal integrator VolumeIntegrator &integ; @@ -59,16 +64,28 @@ class VolumeIntegratorMeshSens : public mfem::LinearFormIntegrator #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( VolumeIntegrator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); + + if (attr_marker == nullptr) + { output_sens.at("mesh_coords") - .AddDomainIntegrator(new VolumeIntegratorMeshSens(primal_integ)); + .AddDomainIntegrator(new VolumeIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new VolumeIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), *attr_marker); + } } class StateIntegrator : public mfem::NonlinearFormIntegrator diff --git a/test/unit/test_common_outputs.cpp b/test/unit/test_common_outputs.cpp index 93e80758..1cd7f6a9 100644 --- a/test/unit/test_common_outputs.cpp +++ b/test/unit/test_common_outputs.cpp @@ -6,8 +6,197 @@ #include "catch.hpp" #include "mfem.hpp" +#include "electromag_test_data.hpp" + #include "common_outputs.hpp" +TEST_CASE("VolumeFunctional::jacobianVectorProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::VolumeFunctional fun(fields, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, v_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + // evaluate d(psi^T R)/dx and contract with v + setInputs(fun, inputs); + double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + +TEST_CASE("VolumeFunctional::vectorJacobianProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::VolumeFunctional fun(fields, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // double dc_loss = calcOutput(fun, inputs); + // std::cout << "dc_loss: " << dc_loss << "\n"; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + // v_tv = 0.0; + // v_tv(0) = 1.0; + mesh_coords.project(v_pert, v_tv); + + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + // evaluate d(psi^T R)/dx and contract with v + mfem::Vector adjoint_tv(1); + adjoint_tv(0) = 1.0; + setInputs(fun, inputs); + vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); + double dfdx_v = wrt_bar * v_tv; + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + TEST_CASE("StateAverageFunctional::calcOutput (3D)") { int num_edge = 3; diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 06b93869..34754086 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -2121,7 +2121,7 @@ TEST_CASE("DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") { DYNAMIC_SECTION("...for degree p = " << p) { - mesh.SetCurvature(p); + // mesh.SetCurvature(p); H1_FECollection fec(p, dim); FiniteElementSpace fes(&mesh, &fec); @@ -2151,7 +2151,7 @@ TEST_CASE("DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") // evaluate dJdx and compute its product with p LinearForm dJdx(&mesh_fes); dJdx.AddDomainIntegrator( - new mach::DCLossFunctionalIntegratorMeshSens(*integ)); + new mach::DCLossFunctionalIntegratorMeshSens(a, *integ)); dJdx.Assemble(); double dJdx_dot_p = dJdx * p; diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp index d9604414..7e305ec9 100644 --- a/test/unit/test_electromag_outputs.cpp +++ b/test/unit/test_electromag_outputs.cpp @@ -9,7 +9,190 @@ #include "electromag_test_data.hpp" -// TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") +TEST_CASE("DCLossFunctional::jacobianVectorProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::DCLossFunctional fun(fields, model, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, v_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + // evaluate d(psi^T R)/dx and contract with v + setInputs(fun, inputs); + double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + +TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::DCLossFunctional fun(fields, model, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // double dc_loss = calcOutput(fun, inputs); + // std::cout << "dc_loss: " << dc_loss << "\n"; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, v_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + // evaluate d(psi^T R)/dx and contract with v + mfem::Vector adjoint_tv(1); + adjoint_tv(0) = 1.0; + setInputs(fun, inputs); + vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); + double dfdx_v = wrt_bar * v_tv; + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + + +// TEST_CASE("Resistivity::vectorJacobianProduct wrt mesh_coords") // { // using namespace mfem; // using namespace electromag_data; @@ -78,20 +261,22 @@ // mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); // mesh_coords.setTrueVec(mesh_coords_tv); -// mach::DCLossFunctional fun(fields, model, {}); +// mach::FunctionalOutput fun(fields.at("state").space(), fields); +// fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); + // mach::MachInputs inputs{ // {"mesh_coords", mesh_coords_tv} // }; -// double dc_loss = calcOutput(fun, inputs); -// std::cout << "dc_loss: " << dc_loss << "\n"; +// // double dc_loss = calcOutput(fun, inputs); +// // std::cout << "dc_loss: " << dc_loss << "\n"; // // initialize the vector that we use to perturb the mesh nodes // VectorFunctionCoefficient v_pert(dim, randVectorState); -// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); -// v_tv = 0.0; -// v_tv(0) = 1.0; +// // mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); +// // v_tv = 0.0; +// // v_tv(0) = 1.0; // // mesh_coords.project(v_pert, v_tv); // // mesh_coords.distributeSharedDofs(mesh_coords_tv); @@ -103,33 +288,60 @@ // adjoint_tv(0) = 1.0; // setInputs(fun, inputs); // vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); -// double dfdx_v = wrt_bar * v_tv; +// // double dfdx_v = wrt_bar * v_tv; -// // now compute the finite-difference approximation... -// mesh_coords_tv.Add(delta, v_tv); -// double dfdx_v_fd_p = calcOutput(fun, inputs); -// std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; +// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); +// v_tv = 0.0; +// v_tv(0) = 1.0; -// mesh_coords_tv.Add(-2 * delta, v_tv); -// // mesh_coords_tv.Add(-delta, v_tv); -// double dfdx_v_fd_m = calcOutput(fun, inputs); -// std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; +// mfem::Vector wrt_bar_fd(mesh_coords.space().GetTrueVSize()); +// wrt_bar_fd = 0.0; -// double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); +// for (int i = 0; i < v_tv.Size(); ++i) +// { +// v_tv = 0.0; +// v_tv(i) = 1.0; + +// // now compute the finite-difference approximation... +// mesh_coords_tv.Add(delta, v_tv); +// double dfdx_v_fd_p = calcOutput(fun, inputs); +// // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + +// mesh_coords_tv.Add(-2 * delta, v_tv); +// // mesh_coords_tv.Add(-delta, v_tv); +// double dfdx_v_fd_m = calcOutput(fun, inputs); +// // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + +// // double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); +// wrt_bar_fd(i) = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + +// mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes +// } + +// std::cout << "wrt_bar:\t"; +// wrt_bar.Print(mfem::out, wrt_bar.Size()); + +// std::cout << "wrt_bar_fd:\t"; +// wrt_bar_fd.Print(mfem::out, wrt_bar.Size()); + +// for (int i = 0; i < v_tv.Size(); ++i) +// { +// REQUIRE(wrt_bar(i) == Approx(wrt_bar_fd(i)).margin(1e-8)); +// } // // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes -// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; -// REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); +// // std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; +// // REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); // } // } // } -// TEST_CASE("Resistivity::vectorJacobianProduct wrt mesh_coords") +// TEST_CASE("Resistivity::jacobianVectorProduct wrt mesh_coords") // { // using namespace mfem; // using namespace electromag_data; -// double delta = 1e-5; +// double delta = 1e-6; // // generate a 6 element mesh // int num_edge = 1; @@ -213,17 +425,15 @@ // // mesh_coords.distributeSharedDofs(mesh_coords_tv); -// mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); -// wrt_bar = 0.0; // // evaluate d(psi^T R)/dx and contract with v -// mfem::Vector adjoint_tv(1); -// adjoint_tv(0) = 1.0; // setInputs(fun, inputs); -// vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); -// double dfdx_v = wrt_bar * v_tv; +// double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); // // now compute the finite-difference approximation... -// mesh_coords_tv.Add(delta, v_tv); +// mesh_coords_tv.Add(2*delta, v_tv); +// double dfdx_v_fd_p_2 = calcOutput(fun, inputs); + +// mesh_coords_tv.Add(-delta, v_tv); // double dfdx_v_fd_p = calcOutput(fun, inputs); // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; @@ -232,130 +442,15 @@ // double dfdx_v_fd_m = calcOutput(fun, inputs); // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; -// double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); +// mesh_coords_tv.Add(-delta, v_tv); +// double dfdx_v_fd_m_2 = calcOutput(fun, inputs); -// // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes -// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; +// double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); +// double dfdx_v_fd_4 = (-dfdx_v_fd_p_2 + 8*dfdx_v_fd_p - 8*dfdx_v_fd_m + dfdx_v_fd_m_2) / (12 * delta); + +// mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes +// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << " dfdx_v_fd_4: " << dfdx_v_fd_4 << "\n"; // REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); // } // } -// } - -TEST_CASE("Resistivity::jacobianVectorProduct wrt mesh_coords") -{ - using namespace mfem; - using namespace electromag_data; - - double delta = 1e-6; - - // generate a 6 element mesh - int num_edge = 1; - auto smesh = Mesh::MakeCartesian2D(num_edge, - num_edge, - Element::TRIANGLE); - mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); - - mesh.EnsureNodes(); - const auto dim = mesh.SpaceDimension(); - - mfem::FunctionCoefficient model( - [](const mfem::Vector &x) - { - // double q = 0; - // for (int i = 0; i < x.Size(); ++i) - // { - - // // q += pow(x(i), 2); - // // q += sqrt(x(i)); - // // q += pow(x(i), 5); - // } - // return q; - return exp(-pow(x(0),2)); - }, - [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) - { - // for (int i = 0; i < x.Size(); ++i) - // { - // x_bar(i) += q_bar * 2 * x(i); - // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); - // // x_bar(i) += q_bar * 5 * pow(x(i), 4); - // } - x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); - - }); - - for (int p = 1; p <= 4; ++p) - { - DYNAMIC_SECTION( "...for degree p = " << p ) - { - - mfem::H1_FECollection fec(p, dim); - mfem::ParFiniteElementSpace fes(&mesh, &fec); - - std::map fields; - fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); - - auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); - auto *mesh_fespace = mesh_gf.ParFESpace(); - /// create new state vector copying the mesh's fe space - fields.emplace("mesh_coords", - mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); - auto &mesh_coords = fields.at("mesh_coords"); - /// set the values of the new GF to those of the mesh's old nodes - mesh_coords.gridFunc() = mesh_gf; - /// tell the mesh to use this GF for its Nodes - /// (and that it doesn't own it) - mesh.NewNodes(mesh_coords.gridFunc(), false); - - mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); - mesh_coords.setTrueVec(mesh_coords_tv); - - mach::FunctionalOutput fun(fields.at("state").space(), fields); - fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); - - mach::MachInputs inputs{ - {"mesh_coords", mesh_coords_tv} - }; - - double dc_loss = calcOutput(fun, inputs); - std::cout << "dc_loss: " << dc_loss << "\n"; - - // initialize the vector that we use to perturb the mesh nodes - VectorFunctionCoefficient v_pert(dim, randVectorState); - - mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); - v_tv = 0.0; - v_tv(0) = 1.0; - // mesh_coords.project(v_pert, v_tv); - - // mesh_coords.distributeSharedDofs(mesh_coords_tv); - - // evaluate d(psi^T R)/dx and contract with v - setInputs(fun, inputs); - double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); - - // now compute the finite-difference approximation... - mesh_coords_tv.Add(2*delta, v_tv); - double dfdx_v_fd_p_2 = calcOutput(fun, inputs); - - mesh_coords_tv.Add(-delta, v_tv); - double dfdx_v_fd_p = calcOutput(fun, inputs); - std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; - - mesh_coords_tv.Add(-2 * delta, v_tv); - // mesh_coords_tv.Add(-delta, v_tv); - double dfdx_v_fd_m = calcOutput(fun, inputs); - std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; - - mesh_coords_tv.Add(-delta, v_tv); - double dfdx_v_fd_m_2 = calcOutput(fun, inputs); - - double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); - double dfdx_v_fd_4 = (-dfdx_v_fd_p_2 + 8*dfdx_v_fd_p - 8*dfdx_v_fd_m + dfdx_v_fd_m_2) / (12 * delta); - - // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes - std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << " dfdx_v_fd_4: " << dfdx_v_fd_4 << "\n"; - REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); - } - } -} \ No newline at end of file +// } \ No newline at end of file diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index eaf1e64b..61437fb5 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -114,7 +114,7 @@ TEST_CASE("VolumeIntegratorMeshSens::AssembleRHSElementVect (2D)") { DYNAMIC_SECTION("...for degree p = " << p) { - mesh.SetCurvature(p); + // mesh.SetCurvature(p); H1_FECollection fec(p, dim); FiniteElementSpace fes(&mesh, &fec); @@ -144,7 +144,7 @@ TEST_CASE("VolumeIntegratorMeshSens::AssembleRHSElementVect (2D)") // evaluate dJdx and compute its product with p LinearForm dJdx(&mesh_fes); dJdx.AddDomainIntegrator( - new mach::VolumeIntegratorMeshSens(*integ)); + new mach::VolumeIntegratorMeshSens(a, *integ)); dJdx.Assemble(); double dJdx_dot_p = dJdx * p; From a68da12e82f379d4d35abd3c1bac31f72597eb33 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Sun, 18 Sep 2022 20:37:15 -0400 Subject: [PATCH 52/72] make format --- src/physics/electromagnetics/electromag_integ.cpp | 3 ++- src/physics/electromagnetics/electromag_integ.hpp | 9 +++++---- src/physics/mfem_common_integ.hpp | 11 ++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index f5b03ed9..7ac4be7b 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3530,7 +3530,8 @@ void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( double sigma_v_bar = -fun_bar * w / pow(sigma_v, 2); // std::cout << "sigma_v_bar: " << sigma_v_bar << "\n"; - // std::cout << "fun_bar: " << fun_bar << " w: " << w << " sigma_v: " << sigma_v << "\n"; + // std::cout << "fun_bar: " << fun_bar << " w: " << w << " sigma_v: " << + // sigma_v << "\n"; /// const double sigma_v = sigma.Eval(trans, ip); PointMat_bar = 0.0; diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 8fc0efaf..1b5543de 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1151,14 +1151,15 @@ inline void addDomainSensitivityIntegrator( if (attr_marker == nullptr) { output_sens.at("mesh_coords") - .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); } else { output_sens.at("mesh_coords") - .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ), *attr_marker); + .AddDomainIntegrator(new DCLossFunctionalIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); } } diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 943fb1fb..9cace017 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -76,15 +76,16 @@ inline void addDomainSensitivityIntegrator( if (attr_marker == nullptr) { - output_sens.at("mesh_coords") - .AddDomainIntegrator(new VolumeIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + output_sens.at("mesh_coords") + .AddDomainIntegrator(new VolumeIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); } else { output_sens.at("mesh_coords") - .AddDomainIntegrator(new VolumeIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ), *attr_marker); + .AddDomainIntegrator(new VolumeIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); } } From 8be10860c1ec1de369a22bc407182d9f1a44fad7 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 19 Sep 2022 13:05:42 -0400 Subject: [PATCH 53/72] added and tested all sensitivities for the ACLossFunctional --- src/common/functional_output.cpp | 2 +- .../electromagnetics/electromag_integ.cpp | 114 ++- .../electromagnetics/electromag_integ.hpp | 61 ++ .../electromagnetics/electromag_outputs.cpp | 644 +++++++++++- .../electromagnetics/electromag_outputs.hpp | 30 +- test/unit/electromag_test_data.hpp | 5 + test/unit/test_electromag_integ.cpp | 93 ++ test/unit/test_electromag_outputs.cpp | 955 ++++++++++++------ 8 files changed, 1547 insertions(+), 357 deletions(-) diff --git a/src/common/functional_output.cpp b/src/common/functional_output.cpp index b519e783..00b616b7 100644 --- a/src/common/functional_output.cpp +++ b/src/common/functional_output.cpp @@ -55,7 +55,7 @@ double calcOutput(FunctionalOutput &output, const MachInputs &inputs) setInputs(output, inputs); Vector state; setVectorFromInputs(inputs, "state", state); - if (state.Size() == 0) + if (state.Size() != output.output.ParFESpace()->GetTrueVSize()) { output.scratch.SetSize(output.output.ParFESpace()->GetTrueVSize()); state.NewDataAndSize(output.scratch.GetData(), diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 7ac4be7b..759dcd82 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3657,7 +3657,8 @@ double ACLossFunctionalIntegrator::GetElementEnergy( trans.SetIntPoint(&ip); /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; el.CalcPhysShape(trans, shape); const auto b_mag = shape * elfun; @@ -3670,6 +3671,117 @@ double ACLossFunctionalIntegrator::GetElementEnergy( return fun; } +void ACLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int ndof = el.GetDof(); + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape; + mfem::Vector shape_bar; +#else + auto &shape = integ.shape; +#endif + + shape.SetSize(ndof); + shape_bar.SetSize(ndof); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &sigma = integ.sigma; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; + + el.CalcPhysShape(trans, shape); + const double b_mag = shape * elfun; + + const double sigma_v = sigma.Eval(trans, ip); + + const double loss = sigma_v * pow(b_mag, 2); + // fun += loss * w; + + /// Start reverse pass... + double fun_bar = 1.0; + + /// fun += loss * w; + double loss_bar = fun_bar * w; + double w_bar = fun_bar * loss; + + /// const double loss = sigma_v * pow(b_mag, 2); + double sigma_v_bar = loss_bar * pow(b_mag, 2); + double b_mag_bar = loss_bar * sigma_v * 2 * b_mag; + + /// const double sigma_v = sigma.Eval(trans, ip); + PointMat_bar = 0.0; + sigma.EvalRevDiff(sigma_v_bar, trans, ip, PointMat_bar); + + /// const double b_mag = shape * elfun; + shape_bar = 0.0; + shape_bar.Add(b_mag_bar, elfun); + + /// el.CalcPhysShape(trans, shape); + el.CalcPhysShapeRevDiff(trans, shape_bar, PointMat_bar); + + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; + + /// double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setInputs(ACLossFunctionalDistributionIntegrator &integ, const MachInputs &inputs) { diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 1b5543de..74c47264 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1228,8 +1228,69 @@ class ACLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator #ifndef MFEM_THREAD_SAFE mfem::Vector shape; #endif + friend class ACLossFunctionalIntegratorMeshSens; }; +class ACLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] state - the state grid function + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + ACLossFunctionalIntegratorMeshSens(mfem::GridFunction &state, + ACLossFunctionalIntegrator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] mesh_el - the finite element that describes the mesh element + /// \param[in] mesh_trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// State GridFunction, needed to get integration order for each element + mfem::GridFunction &state; + /// reference to primal integrator + ACLossFunctionalIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector elfun; + mfem::Vector shape_bar; + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addDomainSensitivityIntegrator( + ACLossFunctionalIntegrator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new ACLossFunctionalIntegratorMeshSens( + fields.at("peak_flux").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new ACLossFunctionalIntegratorMeshSens( + fields.at("peak_flux").gridFunc(), primal_integ), + *attr_marker); + } +} + class ACLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator { public: diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 58dceb21..35391dbb 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -207,11 +207,11 @@ double jacobianVectorProduct(DCLossFunctional &output, } } -void jacobianVectorProduct(DCLossFunctional &output, - const mfem::Vector &wrt_dot, - const std::string &wrt, - mfem::Vector &out_dot) -{ } +// void jacobianVectorProduct(DCLossFunctional &output, +// const mfem::Vector &wrt_dot, +// const std::string &wrt, +// mfem::Vector &out_dot) +// { } double vectorJacobianProduct(DCLossFunctional &output, const mfem::Vector &out_bar, @@ -498,6 +498,9 @@ void setOptions(ACLossFunctional &output, const nlohmann::json &options) void setInputs(ACLossFunctional &output, const MachInputs &inputs) { + output.inputs = inputs; + output.inputs["state"] = inputs.at("peak_flux"); + setValueFromInputs(inputs, "strand_radius", output.radius); setValueFromInputs(inputs, "frequency", output.freq); setValueFromInputs(inputs, "stack_length", output.stack_length); @@ -510,44 +513,610 @@ void setInputs(ACLossFunctional &output, const MachInputs &inputs) double calcOutput(ACLossFunctional &output, const MachInputs &inputs) { - auto fun_inputs = inputs; - fun_inputs["state"] = inputs.at("peak_flux"); - - mfem::Vector flux_state; - setVectorFromInputs(inputs, "peak_flux", flux_state, false, true); - auto &flux_mag = output.fields.at("peak_flux"); - flux_mag.distributeSharedDofs(flux_state); - mfem::ParaViewDataCollection pv("FluxMag", &flux_mag.mesh()); - pv.SetPrefixPath("ParaView"); - pv.SetLevelsOfDetail(3); - pv.SetDataFormat(mfem::VTKFormat::BINARY); - pv.SetHighOrderOutput(true); - pv.RegisterField("FluxMag", &flux_mag.gridFunc()); - pv.Save(); - - double loss = calcOutput(output.output, fun_inputs); - - loss *= output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; - loss *= 2 * output.strands_in_hand * output.num_turns * output.num_slots; - - double volume = calcOutput(output.volume, fun_inputs); + setInputs(output, inputs); + + // mfem::Vector flux_state; + // setVectorFromInputs(inputs, "peak_flux", flux_state, false, true); + // auto &flux_mag = output.fields.at("peak_flux"); + // flux_mag.distributeSharedDofs(flux_state); + // mfem::ParaViewDataCollection pv("FluxMag", &flux_mag.mesh()); + // pv.SetPrefixPath("ParaView"); + // pv.SetLevelsOfDetail(3); + // pv.SetDataFormat(mfem::VTKFormat::BINARY); + // pv.SetHighOrderOutput(true); + // pv.RegisterField("FluxMag", &flux_mag.gridFunc()); + // pv.Save(); + + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * pow(2 * M_PI * output.freq, 2) / + 32.0; + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + return loss / volume; } -double calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs) +double jacobianVectorProduct(ACLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) { - return calcOutputPartial(output.output, wrt, inputs); + if (wrt.rfind("strand_radius", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double strand_loss_dot = + 4 * sigma_b2 * output.stack_length * M_PI * pow(output.radius, 3) * + pow(2 * M_PI * output.freq, 2) / 32.0 * wrt_dot(0); + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + double loss_dot = num_strands * strand_loss_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("frequency", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double strand_loss_dot = 2 * sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 3) * output.freq * + pow(2 * M_PI, 2) / 32.0 * wrt_dot(0); + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + double loss_dot = num_strands * strand_loss_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("stack_length", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double strand_loss_dot = sigma_b2 * M_PI * pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0 * + wrt_dot(0); + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + double loss_dot = num_strands * strand_loss_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("strands_in_hand", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double num_strands_dot = + 2 * output.num_turns * output.num_slots * wrt_dot(0); + + // double loss = num_strands * strand_loss; + double loss_dot = strand_loss * num_strands_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("num_turns", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double num_strands_dot = + 2 * output.strands_in_hand * output.num_slots * wrt_dot(0); + + // double loss = num_strands * strand_loss; + double loss_dot = strand_loss * num_strands_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("num_slots", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double num_strands_dot = + 2 * output.strands_in_hand * output.num_turns * wrt_dot(0); + + // double loss = num_strands * strand_loss; + double loss_dot = strand_loss * num_strands_dot; + + double volume = calcOutput(output.volume, output.inputs); + + return loss_dot / volume; + } + else if (wrt.rfind("mesh_coords", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + double sigma_b2_dot = jacobianVectorProduct(output.output, wrt_dot, wrt); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + + double strand_loss_dot = + output.stack_length * M_PI * pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0 * sigma_b2_dot; + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double loss = num_strands * strand_loss; + double loss_dot = num_strands * strand_loss_dot; + + double volume = calcOutput(output.volume, output.inputs); + double volume_dot = jacobianVectorProduct(output.volume, wrt_dot, wrt); + + return loss_dot / volume - loss / pow(volume, 2) * volume_dot; + } + else + { + return 0.0; + } } -void calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial) +double vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt) { - calcOutputPartial(output.output, wrt, inputs, partial); + if (wrt.rfind("strand_radius", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + // double num_strands_bar = loss_bar * strand_loss; + double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double strand_radius_bar = + strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + + return strand_radius_bar; + } + else if (wrt.rfind("frequency", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + // double num_strands_bar = loss_bar * strand_loss; + double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + M_PI * pow(output.radius, 4) * 2 * output.freq * + pow(2 * M_PI, 2) / 32.0; + + return frequency_bar; + } + else if (wrt.rfind("stack_length", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + // double strand_loss = sigma_b2 * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + // double num_strands_bar = loss_bar * strand_loss; + double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + + return stack_length_bar; + } + else if (wrt.rfind("strands_in_hand", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + double num_strands_bar = loss_bar * strand_loss; + // double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + double strands_in_hand_bar = + num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + return strands_in_hand_bar; + } + else if (wrt.rfind("num_turns", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + double num_strands_bar = loss_bar * strand_loss; + // double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + double num_turns_bar = + num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + return num_turns_bar; + } + else if (wrt.rfind("num_slots", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double num_strands = + // 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + // double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // volume does not depend on any of the inputs except mesh coords + + /// double loss = num_strands * strand_loss; + double num_strands_bar = loss_bar * strand_loss; + // double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + double num_slots_bar = + num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + return num_slots_bar; + } + else + { + return 0.0; + } +} + +void vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + if (wrt.rfind("mesh_coords", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + mfem::Vector vol_bar_vec(&volume_bar, 1); + vectorJacobianProduct(output.volume, vol_bar_vec, wrt, wrt_bar); + + /// double loss = num_strands * strand_loss; + // double num_strands_bar = loss_bar * strand_loss; + double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + + /// double sigma_b2 = calcOutput(output.output, output.inputs); + mfem::Vector sigma_b2_bar_vec(&sigma_b2_bar, 1); + vectorJacobianProduct(output.output, sigma_b2_bar_vec, wrt, wrt_bar); + } } ACLossFunctional::ACLossFunctional( @@ -555,8 +1124,7 @@ ACLossFunctional::ACLossFunctional( mfem::Coefficient &sigma, const nlohmann::json &options) : output(fields.at("peak_flux").space(), fields), - volume(fields, options), - fields(fields) + volume(fields, options) { if (options.contains("attributes")) { diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 0e697fbf..dab29152 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -249,10 +249,10 @@ class DCLossFunctional final const mfem::Vector &wrt_dot, const std::string &wrt); - friend void jacobianVectorProduct(DCLossFunctional &output, - const mfem::Vector &wrt_dot, - const std::string &wrt, - mfem::Vector &out_dot); + // friend void jacobianVectorProduct(DCLossFunctional &output, + // const mfem::Vector &wrt_dot, + // const std::string &wrt, + // mfem::Vector &out_dot); friend double vectorJacobianProduct(DCLossFunctional &output, const mfem::Vector &out_bar, @@ -294,14 +294,18 @@ class ACLossFunctional final friend double calcOutput(ACLossFunctional &output, const MachInputs &inputs); - friend double calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs); + friend double jacobianVectorProduct(ACLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); - friend void calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial); + friend double vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt); + + friend void vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); ACLossFunctional(std::map &fields, mfem::Coefficient &sigma, @@ -311,14 +315,14 @@ class ACLossFunctional final FunctionalOutput output; VolumeFunctional volume; - std::map &fields; - double freq = 1.0; double radius = 1.0; double stack_length = 1.0; double strands_in_hand = 1.0; double num_turns = 1.0; double num_slots = 1.0; + + MachInputs inputs; }; class CoreLossFunctional final diff --git a/test/unit/electromag_test_data.hpp b/test/unit/electromag_test_data.hpp index 38165bae..613c3b2c 100644 --- a/test/unit/electromag_test_data.hpp +++ b/test/unit/electromag_test_data.hpp @@ -15,6 +15,11 @@ namespace electromag_data static std::default_random_engine gen; static std::uniform_real_distribution uniform_rand(-1.0,1.0); +double randNumber() +{ + return uniform_rand(gen); +} + void randBaselineVectorPert(const mfem::Vector &x, mfem::Vector &u) { const double scale = 0.5; diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 34754086..c0918cff 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -2174,6 +2174,99 @@ TEST_CASE("DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") } } +TEST_CASE("ACLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + // q += sqrt(x(i)); + // q += pow(x(i), 5); + } + return q; + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); + // x_bar(i) += q_bar * 5 * pow(x(i), 4); + } + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + // mesh.SetCurvature(p); + + L2_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::ACLossFunctionalIntegrator(model); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::ACLossFunctionalIntegratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("ForceIntegrator3::GetElementEnergy") { using namespace mfem; diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp index 7e305ec9..ff105731 100644 --- a/test/unit/test_electromag_outputs.cpp +++ b/test/unit/test_electromag_outputs.cpp @@ -3,13 +3,14 @@ #include "catch.hpp" #include "finite_element_state.hpp" #include "functional_output.hpp" +#include "magnetostatic_load.hpp" #include "mfem.hpp" #include "electromag_outputs.hpp" #include "electromag_test_data.hpp" -TEST_CASE("DCLossFunctional::jacobianVectorProduct wrt mesh_coords") +TEST_CASE("DCLossFunctional sensitivity wrt mesh_coords") { using namespace mfem; using namespace electromag_data; @@ -67,36 +68,539 @@ TEST_CASE("DCLossFunctional::jacobianVectorProduct wrt mesh_coords") {"mesh_coords", mesh_coords_tv} }; + // // initialize the vector that we use to perturb the mesh nodes + // VectorFunctionCoefficient v_pert(dim, randVectorState); + // mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + // mesh_coords.project(v_pert, v_tv); + // mesh_coords.distributeSharedDofs(mesh_coords_tv); + + // // evaluate d(psi^T R)/dx and contract with v + // setInputs(fun, inputs); + // double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + + // // now compute the finite-difference approximation... + // mesh_coords_tv.Add(delta, v_tv); + // double dfdx_v_fd_p = calcOutput(fun, inputs); + // // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + // mesh_coords_tv.Add(-2 * delta, v_tv); + // // mesh_coords_tv.Add(-delta, v_tv); + // double dfdx_v_fd_m = calcOutput(fun, inputs); + // // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + // double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + // std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + // REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + // initialize the vector that we use to perturb the mesh nodes VectorFunctionCoefficient v_pert(dim, randVectorState); - mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); - mesh_coords.project(v_pert, v_tv); + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, pert_vec); mesh_coords.distributeSharedDofs(mesh_coords_tv); - // evaluate d(psi^T R)/dx and contract with v + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "mesh_coords"); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(fun, adjoint_vec, "mesh_coords", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); + + mesh_coords_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + mesh_coords_tv.Add(delta, pert_vec); // remember to reset the mesh nodes + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt strand_radius") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double strand_radius = 1.0; + mach::MachInputs inputs{ + {"strand_radius", strand_radius}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "strand_radius"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "strand_radius") * pert; + + // now compute the finite-difference approximation... + inputs["strand_radius"] = strand_radius + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["strand_radius"] = strand_radius - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt frequency") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double frequency = 1.0; + mach::MachInputs inputs{ + {"frequency", frequency}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "frequency"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "frequency") * pert; + + // now compute the finite-difference approximation... + inputs["frequency"] = frequency + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["frequency"] = frequency - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt stack_length") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double stack_length = 1.0; + mach::MachInputs inputs{ + {"stack_length", stack_length}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "stack_length"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "stack_length") * pert; + + // now compute the finite-difference approximation... + inputs["stack_length"] = stack_length + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["stack_length"] = stack_length - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt strands_in_hand") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double strands_in_hand = 1.0; + mach::MachInputs inputs{ + {"strands_in_hand", strands_in_hand}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "strands_in_hand"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "strands_in_hand") * pert; + + // now compute the finite-difference approximation... + inputs["strands_in_hand"] = strands_in_hand + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["strands_in_hand"] = strands_in_hand - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt num_turns") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double num_turns = 1.0; + mach::MachInputs inputs{ + {"num_turns", num_turns}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + setInputs(fun, inputs); - double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "num_turns"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "num_turns") * pert; // now compute the finite-difference approximation... - mesh_coords_tv.Add(delta, v_tv); - double dfdx_v_fd_p = calcOutput(fun, inputs); - // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + inputs["num_turns"] = num_turns + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); - mesh_coords_tv.Add(-2 * delta, v_tv); - // mesh_coords_tv.Add(-delta, v_tv); - double dfdx_v_fd_m = calcOutput(fun, inputs); - // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + inputs["num_turns"] = num_turns - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); - double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); - mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes - std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; - REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); } } } -TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") +TEST_CASE("ACLossFunctional sensitivity wrt num_slots") { using namespace mfem; using namespace electromag_data; @@ -134,6 +638,112 @@ TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") std::map fields; fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mach::ACLossFunctional fun(fields, model, {}); + + double num_slots = 1.0; + mach::MachInputs inputs{ + {"num_slots", num_slots}, + {"peak_flux", peak_flux_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "num_slots"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "num_slots") * pert; + + // now compute the finite-difference approximation... + inputs["num_slots"] = num_slots + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["num_slots"] = num_slots - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("ACLossFunctional sensitivity wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return 1.0; + // return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + // x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); auto *mesh_fespace = mesh_gf.ParFESpace(); /// create new state vector copying the mesh's fe space @@ -149,308 +759,45 @@ TEST_CASE("DCLossFunctional::vectorJacobianProduct wrt mesh_coords") mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); mesh_coords.setTrueVec(mesh_coords_tv); - mach::DCLossFunctional fun(fields, model, {}); + mach::ACLossFunctional fun(fields, model, {}); mach::MachInputs inputs{ + {"state", peak_flux_tv}, + {"peak_flux", peak_flux_tv}, {"mesh_coords", mesh_coords_tv} }; - // double dc_loss = calcOutput(fun, inputs); - // std::cout << "dc_loss: " << dc_loss << "\n"; - // initialize the vector that we use to perturb the mesh nodes VectorFunctionCoefficient v_pert(dim, randVectorState); - mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); - mesh_coords.project(v_pert, v_tv); + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, pert_vec); mesh_coords.distributeSharedDofs(mesh_coords_tv); + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "mesh_coords"); + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); wrt_bar = 0.0; - // evaluate d(psi^T R)/dx and contract with v - mfem::Vector adjoint_tv(1); - adjoint_tv(0) = 1.0; - setInputs(fun, inputs); - vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); - double dfdx_v = wrt_bar * v_tv; + vectorJacobianProduct(fun, adjoint_vec, "mesh_coords", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + // now compute the finite-difference approximation... - mesh_coords_tv.Add(delta, v_tv); - double dfdx_v_fd_p = calcOutput(fun, inputs); - // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + mesh_coords_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); - mesh_coords_tv.Add(-2 * delta, v_tv); - // mesh_coords_tv.Add(-delta, v_tv); - double dfdx_v_fd_m = calcOutput(fun, inputs); - // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + mesh_coords_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); - double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); - mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes - std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; - REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + mesh_coords_tv.Add(delta, pert_vec); // remember to reset the mesh nodes + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); } } } - - -// TEST_CASE("Resistivity::vectorJacobianProduct wrt mesh_coords") -// { -// using namespace mfem; -// using namespace electromag_data; - -// double delta = 1e-5; - -// // generate a 6 element mesh -// int num_edge = 1; -// auto smesh = Mesh::MakeCartesian2D(num_edge, -// num_edge, -// Element::TRIANGLE); -// mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); - -// mesh.EnsureNodes(); -// const auto dim = mesh.SpaceDimension(); - -// mfem::FunctionCoefficient model( -// [](const mfem::Vector &x) -// { -// // double q = 0; -// // for (int i = 0; i < x.Size(); ++i) -// // { - -// // // q += pow(x(i), 2); -// // // q += sqrt(x(i)); -// // // q += pow(x(i), 5); -// // } -// // return q; -// return exp(-pow(x(0),2)); -// }, -// [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) -// { -// // for (int i = 0; i < x.Size(); ++i) -// // { -// // x_bar(i) += q_bar * 2 * x(i); -// // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); -// // // x_bar(i) += q_bar * 5 * pow(x(i), 4); -// // } -// x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); - -// }); - -// for (int p = 1; p <= 4; ++p) -// { -// DYNAMIC_SECTION( "...for degree p = " << p ) -// { - -// mfem::H1_FECollection fec(p, dim); -// mfem::ParFiniteElementSpace fes(&mesh, &fec); - -// std::map fields; -// fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); - -// auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); -// auto *mesh_fespace = mesh_gf.ParFESpace(); -// /// create new state vector copying the mesh's fe space -// fields.emplace("mesh_coords", -// mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); -// auto &mesh_coords = fields.at("mesh_coords"); -// /// set the values of the new GF to those of the mesh's old nodes -// mesh_coords.gridFunc() = mesh_gf; -// /// tell the mesh to use this GF for its Nodes -// /// (and that it doesn't own it) -// mesh.NewNodes(mesh_coords.gridFunc(), false); - -// mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); -// mesh_coords.setTrueVec(mesh_coords_tv); - -// mach::FunctionalOutput fun(fields.at("state").space(), fields); -// fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); - -// mach::MachInputs inputs{ -// {"mesh_coords", mesh_coords_tv} -// }; - -// // double dc_loss = calcOutput(fun, inputs); -// // std::cout << "dc_loss: " << dc_loss << "\n"; - -// // initialize the vector that we use to perturb the mesh nodes -// VectorFunctionCoefficient v_pert(dim, randVectorState); - -// // mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); -// // v_tv = 0.0; -// // v_tv(0) = 1.0; -// // mesh_coords.project(v_pert, v_tv); - -// // mesh_coords.distributeSharedDofs(mesh_coords_tv); - -// mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); -// wrt_bar = 0.0; -// // evaluate d(psi^T R)/dx and contract with v -// mfem::Vector adjoint_tv(1); -// adjoint_tv(0) = 1.0; -// setInputs(fun, inputs); -// vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); -// // double dfdx_v = wrt_bar * v_tv; - -// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); -// v_tv = 0.0; -// v_tv(0) = 1.0; - -// mfem::Vector wrt_bar_fd(mesh_coords.space().GetTrueVSize()); -// wrt_bar_fd = 0.0; - -// for (int i = 0; i < v_tv.Size(); ++i) -// { -// v_tv = 0.0; -// v_tv(i) = 1.0; - -// // now compute the finite-difference approximation... -// mesh_coords_tv.Add(delta, v_tv); -// double dfdx_v_fd_p = calcOutput(fun, inputs); -// // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; - -// mesh_coords_tv.Add(-2 * delta, v_tv); -// // mesh_coords_tv.Add(-delta, v_tv); -// double dfdx_v_fd_m = calcOutput(fun, inputs); -// // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; - -// // double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); -// wrt_bar_fd(i) = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); - -// mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes -// } - -// std::cout << "wrt_bar:\t"; -// wrt_bar.Print(mfem::out, wrt_bar.Size()); - -// std::cout << "wrt_bar_fd:\t"; -// wrt_bar_fd.Print(mfem::out, wrt_bar.Size()); - -// for (int i = 0; i < v_tv.Size(); ++i) -// { -// REQUIRE(wrt_bar(i) == Approx(wrt_bar_fd(i)).margin(1e-8)); -// } - -// // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes -// // std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; -// // REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); -// } -// } -// } - -// TEST_CASE("Resistivity::jacobianVectorProduct wrt mesh_coords") -// { -// using namespace mfem; -// using namespace electromag_data; - -// double delta = 1e-6; - -// // generate a 6 element mesh -// int num_edge = 1; -// auto smesh = Mesh::MakeCartesian2D(num_edge, -// num_edge, -// Element::TRIANGLE); -// mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); - -// mesh.EnsureNodes(); -// const auto dim = mesh.SpaceDimension(); - -// mfem::FunctionCoefficient model( -// [](const mfem::Vector &x) -// { -// // double q = 0; -// // for (int i = 0; i < x.Size(); ++i) -// // { - -// // // q += pow(x(i), 2); -// // // q += sqrt(x(i)); -// // // q += pow(x(i), 5); -// // } -// // return q; -// return exp(-pow(x(0),2)); -// }, -// [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) -// { -// // for (int i = 0; i < x.Size(); ++i) -// // { -// // x_bar(i) += q_bar * 2 * x(i); -// // // x_bar(i) += q_bar * 0.5 / sqrt(x(i)); -// // // x_bar(i) += q_bar * 5 * pow(x(i), 4); -// // } -// x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); - -// }); - -// for (int p = 1; p <= 4; ++p) -// { -// DYNAMIC_SECTION( "...for degree p = " << p ) -// { - -// mfem::H1_FECollection fec(p, dim); -// mfem::ParFiniteElementSpace fes(&mesh, &fec); - -// std::map fields; -// fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); - -// auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); -// auto *mesh_fespace = mesh_gf.ParFESpace(); -// /// create new state vector copying the mesh's fe space -// fields.emplace("mesh_coords", -// mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); -// auto &mesh_coords = fields.at("mesh_coords"); -// /// set the values of the new GF to those of the mesh's old nodes -// mesh_coords.gridFunc() = mesh_gf; -// /// tell the mesh to use this GF for its Nodes -// /// (and that it doesn't own it) -// mesh.NewNodes(mesh_coords.gridFunc(), false); - -// mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); -// mesh_coords.setTrueVec(mesh_coords_tv); - -// mach::FunctionalOutput fun(fields.at("state").space(), fields); -// fun.addOutputDomainIntegrator(new mach::DCLossFunctionalIntegrator(model)); - -// mach::MachInputs inputs{ -// {"mesh_coords", mesh_coords_tv} -// }; - -// double dc_loss = calcOutput(fun, inputs); -// std::cout << "dc_loss: " << dc_loss << "\n"; - -// // initialize the vector that we use to perturb the mesh nodes -// VectorFunctionCoefficient v_pert(dim, randVectorState); - -// mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); -// v_tv = 0.0; -// v_tv(0) = 1.0; -// // mesh_coords.project(v_pert, v_tv); - -// // mesh_coords.distributeSharedDofs(mesh_coords_tv); - -// // evaluate d(psi^T R)/dx and contract with v -// setInputs(fun, inputs); -// double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); - -// // now compute the finite-difference approximation... -// mesh_coords_tv.Add(2*delta, v_tv); -// double dfdx_v_fd_p_2 = calcOutput(fun, inputs); - -// mesh_coords_tv.Add(-delta, v_tv); -// double dfdx_v_fd_p = calcOutput(fun, inputs); -// std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; - -// mesh_coords_tv.Add(-2 * delta, v_tv); -// // mesh_coords_tv.Add(-delta, v_tv); -// double dfdx_v_fd_m = calcOutput(fun, inputs); -// std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; - -// mesh_coords_tv.Add(-delta, v_tv); -// double dfdx_v_fd_m_2 = calcOutput(fun, inputs); - -// double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); -// double dfdx_v_fd_4 = (-dfdx_v_fd_p_2 + 8*dfdx_v_fd_p - 8*dfdx_v_fd_m + dfdx_v_fd_m_2) / (12 * delta); - -// mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes -// std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << " dfdx_v_fd_4: " << dfdx_v_fd_4 << "\n"; -// REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); -// } -// } -// } \ No newline at end of file From c93ef262b784963cccfcc38de7e67c94924d8de2 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 19 Sep 2022 13:06:00 -0400 Subject: [PATCH 54/72] make format --- .../electromagnetics/electromag_outputs.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 35391dbb..e9033041 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -758,7 +758,8 @@ double vectorJacobianProduct(ACLossFunctional &output, double strand_radius_bar = strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; @@ -872,7 +873,8 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; @@ -929,7 +931,8 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; return strands_in_hand_bar; @@ -985,7 +988,8 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; return num_turns_bar; @@ -1041,7 +1045,8 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; return num_slots_bar; @@ -1109,7 +1114,8 @@ void vectorJacobianProduct(ACLossFunctional &output, // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; - // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; @@ -1123,8 +1129,7 @@ ACLossFunctional::ACLossFunctional( std::map &fields, mfem::Coefficient &sigma, const nlohmann::json &options) - : output(fields.at("peak_flux").space(), fields), - volume(fields, options) + : output(fields.at("peak_flux").space(), fields), volume(fields, options) { if (options.contains("attributes")) { From f2b27271b44ac61f44ff3abfca937523f68ce8c8 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 19 Sep 2022 15:10:28 -0400 Subject: [PATCH 55/72] added and verified all CoreLossFunctional sensitivities --- src/common/functional_output.cpp | 24 +- src/common/functional_output.hpp | 11 +- .../electromagnetics/electromag_integ.cpp | 222 ++++++++++- .../electromagnetics/electromag_integ.hpp | 178 ++++++++- .../electromagnetics/electromag_outputs.cpp | 27 +- .../electromagnetics/electromag_outputs.hpp | 20 +- src/physics/mfem_common_integ.hpp | 73 +++- test/unit/test_electromag_outputs.cpp | 360 ++++++++++++++++++ test/unit/test_steinmetz_integ.cpp | 266 +++++++++++++ 9 files changed, 1116 insertions(+), 65 deletions(-) diff --git a/src/common/functional_output.cpp b/src/common/functional_output.cpp index 00b616b7..5be8d9bb 100644 --- a/src/common/functional_output.cpp +++ b/src/common/functional_output.cpp @@ -104,13 +104,33 @@ double jacobianVectorProduct(FunctionalOutput &output, } else { - output.output_sens.at(wrt).Assemble(); - output.output_sens.at(wrt).ParallelAssemble(output.scratch); + if (wrt_dot.Size() == 1) + { + Vector state; + output.func_fields->at("state").setTrueVec(state); + output.scratch(0) = output.output_scalar_sens.at(wrt).GetEnergy(state); + } + else + { + output.output_sens.at(wrt).Assemble(); + output.output_sens.at(wrt).ParallelAssemble(output.scratch); + } } return InnerProduct(output.scratch, wrt_dot); } +double vectorJacobianProduct(FunctionalOutput &output, + const mfem::Vector &out_bar, + const std::string &wrt) +{ + Vector state; + output.func_fields->at("state").setTrueVec(state); + double sens = output.output_scalar_sens.at(wrt).GetEnergy(state); + + return sens * out_bar(0); +} + void vectorJacobianProduct(FunctionalOutput &output, const mfem::Vector &out_bar, const std::string &wrt, diff --git a/src/common/functional_output.hpp b/src/common/functional_output.hpp index 6be3a2ac..f04fbc1c 100644 --- a/src/common/functional_output.hpp +++ b/src/common/functional_output.hpp @@ -39,14 +39,9 @@ class FunctionalOutput const mfem::Vector &wrt_dot, const std::string &wrt); - // friend void jacobianVectorProduct(FunctionalOutput &output, - // const mfem::Vector &wrt_dot, - // const std::string &wrt, - // mfem::Vector &out_dot); - - // friend double vectorJacobianProduct(FunctionalOutput &output, - // const mfem::Vector &out_bar, - // const std::string &wrt); + friend double vectorJacobianProduct(FunctionalOutput &output, + const mfem::Vector &out_bar, + const std::string &wrt); friend void vectorJacobianProduct(FunctionalOutput &output, const mfem::Vector &out_bar, diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 759dcd82..bd83b77f 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -5536,7 +5536,7 @@ double SteinmetzLossIntegrator::GetElementEnergy( { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { @@ -5554,7 +5554,8 @@ double SteinmetzLossIntegrator::GetElementEnergy( trans.SetIntPoint(&ip); /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; auto rho_v = rho.Eval(trans, ip); auto k_s_v = k_s.Eval(trans, ip); @@ -5566,6 +5567,223 @@ double SteinmetzLossIntegrator::GetElementEnergy( return fun; } +double SteinmetzLossIntegratorFreqSens::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ + const auto *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &rho = integ.rho; + auto &k_s = integ.k_s; + auto &alpha = integ.alpha; + auto &beta = integ.beta; + auto freq = integ.freq; + auto max_flux_mag = integ.max_flux_mag; + + double sens = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + /// holds quadrature weight + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; + + auto rho_v = rho.Eval(trans, ip); + auto k_s_v = k_s.Eval(trans, ip); + auto alpha_v = alpha.Eval(trans, ip); + auto beta_v = beta.Eval(trans, ip); + + // fun += rho_v * k_s_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) * + // w; + sens += rho_v * k_s_v * alpha_v * pow(freq, alpha_v - 1) * + pow(max_flux_mag, beta_v) * w; + } + return sens; +} + +double SteinmetzLossIntegratorMaxFluxSens::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ + const auto *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &rho = integ.rho; + auto &k_s = integ.k_s; + auto &alpha = integ.alpha; + auto &beta = integ.beta; + auto freq = integ.freq; + auto max_flux_mag = integ.max_flux_mag; + + double sens = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + /// holds quadrature weight + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; + + auto rho_v = rho.Eval(trans, ip); + auto k_s_v = k_s.Eval(trans, ip); + auto alpha_v = alpha.Eval(trans, ip); + auto beta_v = beta.Eval(trans, ip); + + // fun += rho_v * k_s_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) * + // w; + sens += rho_v * k_s_v * pow(freq, alpha_v) * beta_v * + pow(max_flux_mag, beta_v - 1) * w; + } + return sens; +} + +void SteinmetzLossIntegratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); + + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const auto *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &rho = integ.rho; + auto &k_s = integ.k_s; + auto &alpha = integ.alpha; + auto &beta = integ.beta; + auto freq = integ.freq; + auto max_flux_mag = integ.max_flux_mag; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; + + auto rho_v = rho.Eval(trans, ip); + auto k_s_v = k_s.Eval(trans, ip); + auto alpha_v = alpha.Eval(trans, ip); + auto beta_v = beta.Eval(trans, ip); + + // fun += rho_v * k_s_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) * + // w; + + /// Start reverse pass... + /// fun += rho_v * k_s_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) + /// * w; + double fun_bar = 1.0; + double rho_v_bar = + fun_bar * k_s_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) * w; + double k_s_v_bar = + fun_bar * rho_v * pow(freq, alpha_v) * pow(max_flux_mag, beta_v) * w; + // double freq_bar = fun_bar * rho_v * k_s_v * alpha_v * + // pow(freq, alpha_v - 1) * pow(max_flux_mag, beta_v) * + // w; + double alpha_v_bar = fun_bar * rho_v * k_s_v * pow(freq, alpha_v) * + log(freq) * pow(max_flux_mag, beta_v) * w; + // double max_flux_mag_bar = fun_bar * rho_v * k_s_v * pow(freq, alpha_v) + // * + // beta_v * pow(max_flux_mag, beta_v - 1) * w; + double beta_v_bar = fun_bar * rho_v * k_s_v * pow(freq, alpha_v) * + pow(max_flux_mag, beta_v) * log(max_flux_mag) * w; + double w_bar = fun_bar * rho_v * k_s_v * pow(freq, alpha_v) * + pow(max_flux_mag, beta_v); + + /// auto beta_v = beta.Eval(trans, ip); + PointMat_bar = 0.0; + beta.EvalRevDiff(beta_v_bar, trans, ip, PointMat_bar); + + /// auto alpha_v = alpha.Eval(trans, ip); + alpha.EvalRevDiff(alpha_v_bar, trans, ip, PointMat_bar); + + /// auto k_s_v = k_s.Eval(trans, ip); + k_s.EvalRevDiff(k_s_v_bar, trans, ip, PointMat_bar); + + /// auto rho_v = rho.Eval(trans, ip); + rho.EvalRevDiff(rho_v_bar, trans, ip, PointMat_bar); + + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; + + /// double trans_weight = trans.Weight(); + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setInputs(SteinmetzLossDistributionIntegrator &integ, const MachInputs &inputs) { diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 74c47264..1e2a8079 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -730,15 +730,15 @@ class MagneticEnergyIntegratorMeshSens : public mfem::LinearFormIntegrator { } /// \brief - assemble an element's contribution to dJdX - /// \param[in] el - the finite element that describes the mesh element - /// \param[in] trans - the transformation between reference and physical + /// \param[in] mesh_el - the finite element that describes the mesh element + /// \param[in] mesh_trans - the transformation between reference and physical /// space \param[out] mesh_coords_bar - dJdX for the element - void AssembleRHSElementVect(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, + void AssembleRHSElementVect(const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, mfem::Vector &mesh_coords_bar) override; private: - /// state vector for evaluating force + /// state vector for evaluating energy mfem::GridFunction &state; /// reference to primal integrator MagneticEnergyIntegrator &integ; @@ -750,17 +750,29 @@ class MagneticEnergyIntegratorMeshSens : public mfem::LinearFormIntegrator #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( MagneticEnergyIntegrator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator(new MagneticEnergyIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new MagneticEnergyIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new MagneticEnergyIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } } /** commenting out co-energy stuff since I'm stopping maintaining it @@ -1471,17 +1483,29 @@ class ForceIntegratorMeshSens3 : public mfem::LinearFormIntegrator #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( ForceIntegrator3 &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator(new ForceIntegratorMeshSens3( - fields.at("state").gridFunc(), primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new ForceIntegratorMeshSens3( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new ForceIntegratorMeshSens3( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } } /// Functional integrator to compute forces/torques based on the virtual work @@ -1640,14 +1664,132 @@ class SteinmetzLossIntegrator : public mfem::NonlinearFormIntegrator double freq = 1.0; /// Maximum flux density magnitude double max_flux_mag = 1.0; -#ifndef MFEM_THREAD_SAFE - mfem::Vector shape; -#endif + + /// class that implements frequency sensitivities for SteinmetzLossIntegrator + friend class SteinmetzLossIntegratorFreqSens; + + /// class that implements max flux sensitivities for SteinmetzLossIntegrator + friend class SteinmetzLossIntegratorMaxFluxSens; /// class that implements mesh sensitivities for SteinmetzLossIntegrator friend class SteinmetzLossIntegratorMeshSens; }; +class SteinmetzLossIntegratorFreqSens : public mfem::NonlinearFormIntegrator +{ +public: + SteinmetzLossIntegratorFreqSens(SteinmetzLossIntegrator &integ) + : integ(integ) + { } + + /// \brief - Compute element contribution to global sensitivity + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \returns the element contribution to global sensitvity + double GetElementEnergy(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) override; + +private: + /// reference to primal integrator + SteinmetzLossIntegrator &integ; +}; + +class SteinmetzLossIntegratorMaxFluxSens : public mfem::NonlinearFormIntegrator +{ +public: + SteinmetzLossIntegratorMaxFluxSens(SteinmetzLossIntegrator &integ) + : integ(integ) + { } + + /// \brief - Compute element contribution to global sensitivity + /// \param[in] el - the finite element + /// \param[in] trans - defines the reference to physical element mapping + /// \param[in] elfun - state vector of the element + /// \returns the element contribution to global sensitvity + double GetElementEnergy(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) override; + +private: + /// reference to primal integrator + SteinmetzLossIntegrator &integ; +}; + +class SteinmetzLossIntegratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + SteinmetzLossIntegratorMeshSens(mfem::GridFunction &state, + SteinmetzLossIntegrator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] mesh_el - the finite element that describes the mesh element + /// \param[in] mesh_trans - the transformation between reference and physical + /// space \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating loss + mfem::GridFunction &state; + /// reference to primal integrator + SteinmetzLossIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addDomainSensitivityIntegrator( + SteinmetzLossIntegrator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker) +{ + auto &state_fes = fields.at("state").space(); + output_scalar_sens.emplace("frequency", &state_fes); + output_scalar_sens.emplace("max_flux_magnitude", &state_fes); + + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + + if (attr_marker == nullptr) + { + output_scalar_sens.at("frequency") + .AddDomainIntegrator( + new SteinmetzLossIntegratorFreqSens(primal_integ)); + + output_scalar_sens.at("max_flux_magnitude") + .AddDomainIntegrator( + new SteinmetzLossIntegratorMaxFluxSens(primal_integ)); + + output_sens.at("mesh_coords") + .AddDomainIntegrator(new SteinmetzLossIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_scalar_sens.at("frequency") + .AddDomainIntegrator( + new SteinmetzLossIntegratorFreqSens(primal_integ), *attr_marker); + + output_scalar_sens.at("max_flux_magnitude") + .AddDomainIntegrator( + new SteinmetzLossIntegratorMaxFluxSens(primal_integ), + *attr_marker); + + output_sens.at("mesh_coords") + .AddDomainIntegrator(new SteinmetzLossIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } +} + class SteinmetzLossDistributionIntegrator : public mfem::LinearFormIntegrator { public: diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index e9033041..2bc40d70 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1159,19 +1159,26 @@ double calcOutput(CoreLossFunctional &output, const MachInputs &inputs) return calcOutput(output.output, inputs); } -double calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs) +double jacobianVectorProduct(CoreLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) +{ + return jacobianVectorProduct(output.output, wrt_dot, wrt); +} + +double vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt) { - return calcOutputPartial(output.output, wrt, inputs); + return vectorJacobianProduct(output.output, out_bar, wrt); } -void calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial) +void vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) { - calcOutputPartial(output.output, wrt, inputs, partial); + vectorJacobianProduct(output.output, out_bar, wrt, wrt_bar); } CoreLossFunctional::CoreLossFunctional( @@ -1179,7 +1186,7 @@ CoreLossFunctional::CoreLossFunctional( const nlohmann::json &components, const nlohmann::json &materials, const nlohmann::json &options) - : output(fields.at("peak_flux").space(), fields), + : output(fields.at("state").space(), fields), rho(constructMaterialCoefficient("rho", components, materials)), k_s(constructMaterialCoefficient("ks", components, materials)), alpha(constructMaterialCoefficient("alpha", components, materials)), diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index dab29152..548cdd19 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -341,14 +341,18 @@ class CoreLossFunctional final friend double calcOutput(CoreLossFunctional &output, const MachInputs &inputs); - friend double calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs); - - friend void calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial); + friend double jacobianVectorProduct(CoreLossFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); + + friend double vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt); + + friend void vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); CoreLossFunctional(std::map &fields, const nlohmann::json &components, diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 9cace017..57b4fcd2 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -1,6 +1,7 @@ #ifndef MACH_MFEM_COMMON_INTEG #define MACH_MFEM_COMMON_INTEG +#include #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -156,17 +157,29 @@ class MagnitudeCurlStateIntegratorMeshSens : public mfem::LinearFormIntegrator #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( MagnitudeCurlStateIntegrator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator(new MagnitudeCurlStateIntegratorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new MagnitudeCurlStateIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new MagnitudeCurlStateIntegratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } } class IEAggregateIntegratorNumerator : public mfem::NonlinearFormIntegrator @@ -278,18 +291,31 @@ class IECurlMagnitudeAggregateIntegratorNumeratorMeshSens #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( IECurlMagnitudeAggregateIntegratorNumerator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator( - new IECurlMagnitudeAggregateIntegratorNumeratorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorNumeratorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorNumeratorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } } class IECurlMagnitudeAggregateIntegratorDenominator @@ -361,18 +387,31 @@ class IECurlMagnitudeAggregateIntegratorDenominatorMeshSens #endif }; -inline void addSensitivityIntegrator( +inline void addDomainSensitivityIntegrator( IECurlMagnitudeAggregateIntegratorDenominator &primal_integ, std::map &fields, std::map &output_sens, - std::map &output_scalar_sens) + std::map &output_scalar_sens, + mfem::Array *attr_marker) { auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); - output_sens.at("mesh_coords") - .AddDomainIntegrator( - new IECurlMagnitudeAggregateIntegratorDenominatorMeshSens( - fields.at("state").gridFunc(), primal_integ)); + + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorDenominatorMeshSens( + fields.at("state").gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IECurlMagnitudeAggregateIntegratorDenominatorMeshSens( + fields.at("state").gridFunc(), primal_integ), + *attr_marker); + } } class DiffusionIntegratorMeshSens final : public mfem::LinearFormIntegrator diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp index ff105731..84b56528 100644 --- a/test/unit/test_electromag_outputs.cpp +++ b/test/unit/test_electromag_outputs.cpp @@ -801,3 +801,363 @@ TEST_CASE("ACLossFunctional sensitivity wrt mesh_coords") } } } + +TEST_CASE("CoreLossFunctional sensitivity wrt frequency") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + auto &state = fields.at("state"); + mfem::Vector state_tv(state.space().GetTrueVSize()); + state.project(randState, state_tv); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + // mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + // mesh_coords.setTrueVec(mesh_coords_tv); + + auto components = R"({ + "box1": { + "attrs": [1], + "material": "box1" + } + })"_json; + + auto materials = R"({ + "box1": { + "rho": 1.0, + "ks": 1.0, + "alpha": 1.0, + "beta": 1.0 + } + })"_json; + + mach::CoreLossFunctional fun(fields, components, materials, {}); + + double frequency = 2.0 + randNumber(); + mach::MachInputs inputs{ + {"frequency", frequency}, + {"state", state_tv}, + {"peak_flux", peak_flux_tv}, + // {"mesh_coords", mesh_coords_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "frequency"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "frequency") * pert; + + // now compute the finite-difference approximation... + inputs["frequency"] = frequency + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["frequency"] = frequency - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("CoreLossFunctional sensitivity wrt max_flux_magnitude") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + auto &state = fields.at("state"); + mfem::Vector state_tv(state.space().GetTrueVSize()); + state.project(randState, state_tv); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + // mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + // mesh_coords.setTrueVec(mesh_coords_tv); + + auto components = R"({ + "box1": { + "attrs": [1], + "material": "box1" + } + })"_json; + + auto materials = R"({ + "box1": { + "rho": 1.0, + "ks": 1.0, + "alpha": 1.0, + "beta": 1.0 + } + })"_json; + + mach::CoreLossFunctional fun(fields, components, materials, {}); + + double max_flux_magnitude = 2.0 + randNumber(); + mach::MachInputs inputs{ + {"max_flux_magnitude", max_flux_magnitude}, + {"state", state_tv}, + {"peak_flux", peak_flux_tv}, + // {"mesh_coords", mesh_coords_tv} + }; + + double pert = randNumber(); + mfem::Vector pert_vec(&pert, 1); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "max_flux_magnitude"); + double dfdp_rev = vectorJacobianProduct(fun, adjoint_vec, "max_flux_magnitude") * pert; + + // now compute the finite-difference approximation... + inputs["max_flux_magnitude"] = max_flux_magnitude + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["max_flux_magnitude"] = max_flux_magnitude - pert * delta; + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("CoreLossFunctional sensitivity wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return 1.0; + // return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + // x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + auto components = R"({ + "box1": { + "attrs": [1], + "material": "box1" + } + })"_json; + + auto materials = R"({ + "box1": { + "rho": 1.0, + "ks": 1.0, + "alpha": 1.0, + "beta": 1.0 + } + })"_json; + + mach::CoreLossFunctional fun(fields, components, materials, {}); + mach::MachInputs inputs{ + {"state", peak_flux_tv}, + {"peak_flux", peak_flux_tv}, + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, pert_vec); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "mesh_coords"); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(fun, adjoint_vec, "mesh_coords", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); + + mesh_coords_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + mesh_coords_tv.Add(delta, pert_vec); // remember to reset the mesh nodes + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} \ No newline at end of file diff --git a/test/unit/test_steinmetz_integ.cpp b/test/unit/test_steinmetz_integ.cpp index b86ef6f6..6b0b46dd 100644 --- a/test/unit/test_steinmetz_integ.cpp +++ b/test/unit/test_steinmetz_integ.cpp @@ -1,6 +1,8 @@ #include "catch.hpp" #include "mfem.hpp" +#include "electromag_test_data.hpp" + #include "coefficient.hpp" #include "electromag_integ.hpp" #include "material_library.hpp" @@ -42,6 +44,270 @@ TEST_CASE("SteinmetzLossIntegrator::GetElementEnergy") REQUIRE(core_loss == Approx(15.5341269187)); } +TEST_CASE("SteinmetzLossIntegratorFreqSens::GetElementEnergy") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + auto f = [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + } + return q; + }; + + auto f_rev_diff = [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + } + }; + + mfem::FunctionCoefficient rho(f, f_rev_diff); + mfem::FunctionCoefficient k_s(f, f_rev_diff); + mfem::FunctionCoefficient alpha(f, f_rev_diff); + mfem::FunctionCoefficient beta(f, f_rev_diff); + // mfem::ConstantCoefficient rho(1.0); + // mfem::ConstantCoefficient k_s(0.01); + // mfem::ConstantCoefficient alpha(1.21); + // mfem::ConstantCoefficient beta(1.62); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + L2_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::SteinmetzLossIntegrator(rho, k_s, alpha, beta); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // evaluate dJdp and compute its product with pert + NonlinearForm dJdp(&fes); + dJdp.AddDomainIntegrator( + new mach::SteinmetzLossIntegratorFreqSens(*integ)); + + double frequency = 2.0 + randNumber(); + mach::MachInputs inputs{ + {"frequency", frequency}, + }; + setInputs(*integ, inputs); + double dfdp_fwd = dJdp.GetEnergy(a); + + // now compute the finite-difference approximation... + inputs["frequency"] = frequency + delta; + setInputs(*integ, inputs); + double dfdp_fd_p = functional.GetEnergy(a); + + inputs["frequency"] = frequency - delta; + setInputs(*integ, inputs); + double dfdp_fd_m = functional.GetEnergy(a); + + double dfdp_fd = (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + + } + } +} + +TEST_CASE("SteinmetzLossIntegratorMaxFluxSens::GetElementEnergy") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + auto f = [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + } + return q; + }; + + auto f_rev_diff = [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + } + }; + + mfem::FunctionCoefficient rho(f, f_rev_diff); + mfem::FunctionCoefficient k_s(f, f_rev_diff); + mfem::FunctionCoefficient alpha(f, f_rev_diff); + mfem::FunctionCoefficient beta(f, f_rev_diff); + // mfem::ConstantCoefficient rho(1.0); + // mfem::ConstantCoefficient k_s(0.01); + // mfem::ConstantCoefficient alpha(1.21); + // mfem::ConstantCoefficient beta(1.62); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + L2_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::SteinmetzLossIntegrator(rho, k_s, alpha, beta); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // evaluate dJdp and compute its product with pert + NonlinearForm dJdp(&fes); + dJdp.AddDomainIntegrator( + new mach::SteinmetzLossIntegratorMaxFluxSens(*integ)); + + double max_flux_magnitude = 2.0 + randNumber(); + mach::MachInputs inputs{ + {"max_flux_magnitude", max_flux_magnitude}, + }; + setInputs(*integ, inputs); + double dfdp_fwd = dJdp.GetEnergy(a); + + // now compute the finite-difference approximation... + inputs["max_flux_magnitude"] = max_flux_magnitude + delta; + setInputs(*integ, inputs); + double dfdp_fd_p = functional.GetEnergy(a); + + inputs["max_flux_magnitude"] = max_flux_magnitude - delta; + setInputs(*integ, inputs); + double dfdp_fd_m = functional.GetEnergy(a); + + double dfdp_fd = (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + + } + } +} + +TEST_CASE("SteinmetzLossIntegratorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + auto f = [](const mfem::Vector &x) + { + double q = 0; + for (int i = 0; i < x.Size(); ++i) + { + q += pow(x(i), 2); + } + return q; + }; + + auto f_rev_diff = [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + for (int i = 0; i < x.Size(); ++i) + { + x_bar(i) += q_bar * 2 * x(i); + } + }; + + mfem::FunctionCoefficient rho(f, f_rev_diff); + mfem::FunctionCoefficient k_s(f, f_rev_diff); + mfem::FunctionCoefficient alpha(f, f_rev_diff); + mfem::FunctionCoefficient beta(f, f_rev_diff); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + L2_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::SteinmetzLossIntegrator(rho, k_s, alpha, beta); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::SteinmetzLossIntegratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + From d122d90f5885644af85680dc6c6b48bad9136adb Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Tue, 18 Oct 2022 17:54:57 -0400 Subject: [PATCH 56/72] added differentiation to MassFunctional with test --- src/physics/common_outputs.hpp | 15 +++ test/unit/test_common_outputs.cpp | 217 ++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index aceca3fe..c3eb29db 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -81,6 +81,21 @@ class MassFunctional final return calcOutput(output.output, inputs); } + friend double jacobianVectorProduct(MassFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) + { + return jacobianVectorProduct(output.output, wrt_dot, wrt); + } + + friend void vectorJacobianProduct(MassFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) + { + vectorJacobianProduct(output.output, out_bar, wrt, wrt_bar); + } + MassFunctional(std::map &fields, const nlohmann::json &components, const nlohmann::json &materials, diff --git a/test/unit/test_common_outputs.cpp b/test/unit/test_common_outputs.cpp index 1cd7f6a9..ad6e148c 100644 --- a/test/unit/test_common_outputs.cpp +++ b/test/unit/test_common_outputs.cpp @@ -197,6 +197,223 @@ TEST_CASE("VolumeFunctional::vectorJacobianProduct wrt mesh_coords") } } +TEST_CASE("MassFunctional::jacobianVectorProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + auto components = R"({ + "box1": { + "attrs": [1], + "material": "box1" + } + })"_json; + + auto materials = R"({ + "box1": { + "rho": 1.0, + "ks": 1.0, + "alpha": 1.0, + "beta": 1.0 + } + })"_json; + mach::MassFunctional fun(fields, components, materials, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, v_tv); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + // evaluate d(psi^T R)/dx and contract with v + setInputs(fun, inputs); + double dfdx_v = jacobianVectorProduct(fun, v_tv, "mesh_coords"); + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + +TEST_CASE("MassFunctional::vectorJacobianProduct wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + auto components = R"({ + "box1": { + "attrs": [1], + "material": "box1" + } + })"_json; + + auto materials = R"({ + "box1": { + "rho": 1.0, + "ks": 1.0, + "alpha": 1.0, + "beta": 1.0 + } + })"_json; + mach::MassFunctional fun(fields, components, materials, {}); + mach::MachInputs inputs{ + {"mesh_coords", mesh_coords_tv} + }; + + // double dc_loss = calcOutput(fun, inputs); + // std::cout << "dc_loss: " << dc_loss << "\n"; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + + mfem::Vector v_tv(mesh_coords.space().GetTrueVSize()); + // v_tv = 0.0; + // v_tv(0) = 1.0; + mesh_coords.project(v_pert, v_tv); + + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + // evaluate d(psi^T R)/dx and contract with v + mfem::Vector adjoint_tv(1); + adjoint_tv(0) = 1.0; + setInputs(fun, inputs); + vectorJacobianProduct(fun, adjoint_tv, "mesh_coords", wrt_bar); + double dfdx_v = wrt_bar * v_tv; + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, v_tv); + double dfdx_v_fd_p = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_p: " << dfdx_v_fd_p << "\n"; + + mesh_coords_tv.Add(-2 * delta, v_tv); + // mesh_coords_tv.Add(-delta, v_tv); + double dfdx_v_fd_m = calcOutput(fun, inputs); + // std::cout << "dfdx_v_fd_m: " << dfdx_v_fd_m << "\n"; + + double dfdx_v_fd = adjoint_tv(0) * (dfdx_v_fd_p - dfdx_v_fd_m) / (2 * delta); + + // mesh_coords_tv.Add(delta, v_tv); // remember to reset the mesh nodes + std::cout << "dfdx_v: " << dfdx_v << " dfdx_v_fd: " << dfdx_v_fd << "\n"; + REQUIRE(dfdx_v == Approx(dfdx_v_fd).margin(1e-8)); + } + } +} + TEST_CASE("StateAverageFunctional::calcOutput (3D)") { int num_edge = 3; From 28e067cb3795622f9e66a47d168b68783a5821ed Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 12:54:23 -0500 Subject: [PATCH 57/72] extended support for IEAggregateFunctional to handle fields other than the field named state, and differentiated it wrt the input state and the mesh --- src/physics/common_outputs.cpp | 9 +- src/physics/common_outputs.hpp | 5 + src/physics/mfem_common_integ.cpp | 273 ++++++++++++++++++++++++++- src/physics/mfem_common_integ.hpp | 165 +++++++++++++++- test/unit/test_mfem_common_integ.cpp | 266 ++++++++++++++++++++++++++ 5 files changed, 705 insertions(+), 13 deletions(-) diff --git a/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index f547774f..f90ec4a2 100644 --- a/src/physics/common_outputs.cpp +++ b/src/physics/common_outputs.cpp @@ -134,21 +134,22 @@ IEAggregateFunctional::IEAggregateFunctional( : numerator(fes, fields), denominator(fes, fields) { auto rho = options["rho"].get(); + auto state_name = options.value("state", "state"); if (options.contains("attributes")) { auto attributes = options["attributes"].get>(); numerator.addOutputDomainIntegrator( - new IEAggregateIntegratorNumerator(rho), attributes); + new IEAggregateIntegratorNumerator(rho, state_name), attributes); denominator.addOutputDomainIntegrator( - new IEAggregateIntegratorDenominator(rho), attributes); + new IEAggregateIntegratorDenominator(rho, state_name), attributes); } else { numerator.addOutputDomainIntegrator( - new IEAggregateIntegratorNumerator(rho)); + new IEAggregateIntegratorNumerator(rho, state_name)); denominator.addOutputDomainIntegrator( - new IEAggregateIntegratorDenominator(rho)); + new IEAggregateIntegratorDenominator(rho, state_name)); } } diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index c3eb29db..4e96ff9b 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -230,6 +230,11 @@ class IEAggregateFunctional friend double calcOutput(IEAggregateFunctional &output, const MachInputs &inputs) { + mfem::Vector state; + setVectorFromInputs(inputs, "state", state); + double true_max = state.Max(); + setInputs(output, {{"true_max", true_max}}); + double num = calcOutput(output.numerator, inputs); double denom = calcOutput(output.denominator, inputs); return num / denom; diff --git a/src/physics/mfem_common_integ.cpp b/src/physics/mfem_common_integ.cpp index f5694ef9..6cfd99df 100644 --- a/src/physics/mfem_common_integ.cpp +++ b/src/physics/mfem_common_integ.cpp @@ -1,8 +1,11 @@ +#include + #include "mfem.hpp" +#include "nlohmann/json.hpp" -#include "mfem_common_integ.hpp" +#include "mach_input.hpp" -#include +#include "mfem_common_integ.hpp" using namespace mfem; @@ -471,6 +474,11 @@ void setOptions(IEAggregateIntegratorNumerator &integ, } } +void setInputs(IEAggregateIntegratorNumerator &integ, const MachInputs &inputs) +{ + setValueFromInputs(inputs, "true_max", integ.true_max); +} + double IEAggregateIntegratorNumerator::GetElementEnergy( const mfem::FiniteElement &el, mfem::ElementTransformation &trans, @@ -489,13 +497,138 @@ double IEAggregateIntegratorNumerator::GetElementEnergy( { const auto &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + el.CalcShape(ip, shape); - double g = shape * elfun; - fun += ip.weight * trans.Weight() * g * exp(rho * g); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + fun += g * exp_rho_g * w; } return fun; } +void IEAggregateIntegratorNumerator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) +{ +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape(elfun.Size()); +#else + shape.SetSize(elfun.Size()); +#endif + + const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + el.CalcShape(ip, shape); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + /// fun += g * exp_rho_g * w; + const double fun_bar = 1.0; + double g_bar = fun_bar * exp_rho_g * w; + double exp_rho_g_bar = fun_bar * g * w; + // double w_bar = fun_bar * g * exp_rho_g; + + /// double exp_rho_g = exp(rho * (g - true_max)); + g_bar += exp_rho_g_bar * rho * exp_rho_g; + + /// double g = shape * elfun; + elfun_bar.Add(g_bar, shape); + } +} + +void IEAggregateIntegratorNumeratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape; +#else + auto &shape = integ.shape; +#endif + shape.SetSize(elfun.Size()); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + + auto rho = integ.rho; + auto true_max = integ.true_max; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + el.CalcShape(ip, shape); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + /// fun += g * exp_rho_g * w; + const double fun_bar = 1.0; + // double g_bar = fun_bar * exp_rho_g * w; + // double exp_rho_g_bar = fun_bar * g * w; + double w_bar = fun_bar * g * exp_rho_g; + + /// const double w = ip.weight * trans_weight; + + double trans_weight_bar = w_bar * ip.weight; + PointMat_bar = 0.0; + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// double exp_rho_g = exp(rho * (g - true_max)); + // g_bar += exp_rho_g_bar * rho * exp_rho_g; + + /// double g = shape * elfun; + // elfun_bar.Add(g_bar, shape); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setOptions(IEAggregateIntegratorDenominator &integ, const nlohmann::json &options) { @@ -505,6 +638,12 @@ void setOptions(IEAggregateIntegratorDenominator &integ, } } +void setInputs(IEAggregateIntegratorDenominator &integ, + const MachInputs &inputs) +{ + setValueFromInputs(inputs, "true_max", integ.true_max); +} + double IEAggregateIntegratorDenominator::GetElementEnergy( const mfem::FiniteElement &el, mfem::ElementTransformation &trans, @@ -522,13 +661,135 @@ double IEAggregateIntegratorDenominator::GetElementEnergy( { const auto &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + el.CalcShape(ip, shape); - double g = shape * elfun; - fun += ip.weight * trans.Weight() * exp(rho * g); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + fun += exp_rho_g * w; } return fun; } +void IEAggregateIntegratorDenominator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) +{ +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape(elfun.Size()); +#else + shape.SetSize(elfun.Size()); +#endif + + const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + el.CalcShape(ip, shape); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + /// fun += exp_rho_g * w; + double fun_bar = 1.0; + double exp_rho_g_bar = fun_bar * w; + // double w_bar = fun_bar * exp_rho_g; + + /// double exp_rho_g = exp(rho * (g - true_max)); + double g_bar = exp_rho_g_bar * rho * exp_rho_g; + + /// double g = shape * elfun; + elfun_bar.Add(g_bar, shape); + } +} + +void IEAggregateIntegratorDenominatorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) +{ + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int mesh_ndof = mesh_el.GetDof(); + const int ndof = el.GetDof(); + const int dim = el.GetDim(); + const int space_dim = trans.GetSpaceDim(); + + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape; +#else + auto &shape = integ.shape; +#endif + shape.SetSize(elfun.Size()); + PointMat_bar.SetSize(space_dim, mesh_ndof); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + + auto rho = integ.rho; + auto true_max = integ.true_max; + + mesh_coords_bar.SetSize(mesh_ndof * space_dim); + mesh_coords_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + const double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + el.CalcShape(ip, shape); + const double g = shape * elfun; + const double exp_rho_g = exp(rho * (g - true_max)); + + /// fun += exp_rho_g * w; + const double fun_bar = 1.0; + // double exp_rho_g_bar = fun_bar * g * w; + double w_bar = fun_bar * exp_rho_g; + + /// const double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; + PointMat_bar = 0.0; + isotrans.WeightRevDiff(trans_weight_bar, PointMat_bar); + + /// double exp_rho_g = exp(rho * (g - true_max)); + // double g_bar = exp_rho_g_bar * rho * exp_rho_g; + + /// const double g = shape * elfun; + // elfun_bar.Add(g_bar, shape); + + /// code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < mesh_ndof; ++j) + { + for (int d = 0; d < space_dim; ++d) + { + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); + } + } + } +} + void setOptions(IECurlMagnitudeAggregateIntegratorNumerator &integ, const nlohmann::json &options) { diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index 57b4fcd2..a3991b7b 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -1,11 +1,12 @@ #ifndef MACH_MFEM_COMMON_INTEG #define MACH_MFEM_COMMON_INTEG -#include +#include #include "mfem.hpp" #include "nlohmann/json.hpp" #include "finite_element_state.hpp" +#include "mach_input.hpp" #include "mach_integrator.hpp" namespace mach @@ -188,40 +189,198 @@ class IEAggregateIntegratorNumerator : public mfem::NonlinearFormIntegrator friend void setOptions(IEAggregateIntegratorNumerator &integ, const nlohmann::json &options); - IEAggregateIntegratorNumerator(const double rho) : rho(rho) { } + friend void setInputs(IEAggregateIntegratorNumerator &integ, + const MachInputs &inputs); + + IEAggregateIntegratorNumerator(const double rho, + std::string state_name = "state") + : rho(rho), _state_name(std::move(state_name)) + { } double GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + + const std::string &state_name() { return _state_name; } + private: /// aggregation parameter rho double rho; + /// name of state integrating over - needed for mesh sens + std::string _state_name; + /// true max value - used to improve numerical conditioning + double true_max = 1.0; #ifndef MFEM_THREAD_SAFE mfem::Vector shape; #endif + friend class IEAggregateIntegratorNumeratorMeshSens; }; +class IEAggregateIntegratorNumeratorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] state - the state vector to evaluate force at + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrators + IEAggregateIntegratorNumeratorMeshSens(mfem::GridFunction &state, + IEAggregateIntegratorNumerator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating integrator + mfem::GridFunction &state; + /// reference to primal integrator + IEAggregateIntegratorNumerator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun; +#endif +}; + +inline void addDomainSensitivityIntegrator( + IEAggregateIntegratorNumerator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + + const auto &state_name = primal_integ.state_name(); + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new IEAggregateIntegratorNumeratorMeshSens( + fields.at(state_name).gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IEAggregateIntegratorNumeratorMeshSens( + fields.at(state_name).gridFunc(), primal_integ), + *attr_marker); + } +} + class IEAggregateIntegratorDenominator : public mfem::NonlinearFormIntegrator { public: friend void setOptions(IEAggregateIntegratorDenominator &integ, const nlohmann::json &options); - IEAggregateIntegratorDenominator(const double rho) : rho(rho) { } + friend void setInputs(IEAggregateIntegratorDenominator &integ, + const MachInputs &inputs); + + IEAggregateIntegratorDenominator(const double rho, + std::string state_name = "state") + : rho(rho), _state_name(std::move(state_name)) + { } double GetElementEnergy(const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun) override; + void AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) override; + + const std::string &state_name() { return _state_name; } + private: /// aggregation parameter rho double rho; + /// name of state integrating over - needed for mesh sens + std::string _state_name; + /// true max value - used to improve numerical conditioning + double true_max = 1.0; #ifndef MFEM_THREAD_SAFE mfem::Vector shape; #endif + friend class IEAggregateIntegratorDenominatorMeshSens; }; +class IEAggregateIntegratorDenominatorMeshSens : public mfem::LinearFormIntegrator +{ +public: + /// \brief - Compute forces/torques based on the virtual work method + /// \param[in] state - the state vector to evaluate force at + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrators + IEAggregateIntegratorDenominatorMeshSens(mfem::GridFunction &state, + IEAggregateIntegratorDenominator &integ) + : state(state), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] el - the finite element that describes the mesh element + /// \param[in] trans - the transformation between reference and physical + /// space + /// \param[out] mesh_coords_bar - dJdX for the element + void AssembleRHSElementVect(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &mesh_coords_bar) override; + +private: + /// state vector for evaluating integrator + mfem::GridFunction &state; + /// reference to primal integrator + IEAggregateIntegratorDenominator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; + mfem::Array vdofs; + mfem::Vector elfun; +#endif +}; + +inline void addDomainSensitivityIntegrator( + IEAggregateIntegratorDenominator &primal_integ, + std::map &fields, + std::map &output_sens, + std::map &output_scalar_sens, + mfem::Array *attr_marker) +{ + auto &mesh_fes = fields.at("mesh_coords").space(); + output_sens.emplace("mesh_coords", &mesh_fes); + + const auto &state_name = primal_integ.state_name(); + if (attr_marker == nullptr) + { + output_sens.at("mesh_coords") + .AddDomainIntegrator(new IEAggregateIntegratorDenominatorMeshSens( + fields.at(state_name).gridFunc(), primal_integ)); + } + else + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + new IEAggregateIntegratorDenominatorMeshSens( + fields.at(state_name).gridFunc(), primal_integ), + *attr_marker); + } +} + class IECurlMagnitudeAggregateIntegratorNumerator : public mfem::NonlinearFormIntegrator { diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index 61437fb5..8d117363 100644 --- a/test/unit/test_mfem_common_integ.cpp +++ b/test/unit/test_mfem_common_integ.cpp @@ -324,6 +324,272 @@ TEST_CASE("MagnitudeCurlStateIntegratorMeshSens::AssembleRHSElementVect") } } +TEST_CASE("IEAggregateIntegratorNumerator::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + NonlinearForm functional(&fes); + + auto *integ = new mach::IEAggregateIntegratorNumerator(1.0); + setInputs(*integ, {{"true_max", 2.0}}); + functional.AddDomainIntegrator(integ); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + FunctionCoefficient pert(randState); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("IEAggregateIntegratorNumeratorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + // FunctionCoefficient pert(randState); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + auto *integ = new mach::IEAggregateIntegratorNumerator(1.0); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::IEAggregateIntegratorNumeratorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + +TEST_CASE("IEAggregateIntegratorDenominator::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + NonlinearForm functional(&fes); + + auto *integ = new mach::IEAggregateIntegratorDenominator(1.0); + setInputs(*integ, {{"true_max", 2.0}}); + functional.AddDomainIntegrator(integ); + + // initialize the vector that dJdu multiplies + GridFunction p(&fes); + FunctionCoefficient pert(randState); + p.ProjectCoefficient(pert); + + // evaluate dJdu and compute its product with v + GridFunction dJdu(&fes); + functional.Mult(a, dJdu); + double dJdu_dot_p = InnerProduct(dJdu, p); + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + +TEST_CASE("IEAggregateIntegratorDenominatorMeshSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + H1_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + // FunctionCoefficient pert(randState); + FunctionCoefficient a_field([](const mfem::Vector &x){ + return x(0) * x(0) + x(1) * x(1); + }); + a.ProjectCoefficient(a_field); + + double actual_max = a.Max(); + + auto *integ = new mach::IEAggregateIntegratorDenominator(1.0); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&mesh_fes); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&mesh_fes); + p.ProjectCoefficient(v_pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdx(&mesh_fes); + dJdx.AddDomainIntegrator( + new mach::IEAggregateIntegratorDenominatorMeshSens(a, *integ)); + dJdx.Assemble(); + double dJdx_dot_p = dJdx * p; + + // now compute the finite-difference approximation... + GridFunction x_pert(x_nodes); + x_pert.Add(-delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + double dJdx_dot_p_fd = -functional.GetEnergy(a); + x_pert.Add(2 * delta, p); + mesh.SetNodes(x_pert); + fes.Update(); + dJdx_dot_p_fd += functional.GetEnergy(a); + dJdx_dot_p_fd /= (2 * delta); + mesh.SetNodes(x_nodes); // remember to reset the mesh nodes + fes.Update(); + + REQUIRE(dJdx_dot_p == Approx(dJdx_dot_p_fd)); + } + } +} + TEST_CASE("IECurlMagnitudeAggregateIntegratorNumerator::AssembleElementVector") { using namespace mfem; From 9adfcb8bd41aa3dbeaed9bff66987f126def6a7c Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:00:39 -0500 Subject: [PATCH 58/72] updated RelaxedNewton and LineSearch methods to have max iter as an option --- src/common/linesearch.cpp | 9 ++++++--- src/common/linesearch.hpp | 7 +++---- src/common/relaxed_newton.cpp | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/common/linesearch.cpp b/src/common/linesearch.cpp index 2370ac16..212a5cb0 100644 --- a/src/common/linesearch.cpp +++ b/src/common/linesearch.cpp @@ -58,8 +58,7 @@ namespace mach double BacktrackingLineSearch::search(const std::function &phi, double phi0, double dphi0, - double alpha, - int max_iter) + double alpha) { auto alpha1 = alpha; auto alpha2 = alpha; @@ -77,7 +76,11 @@ double BacktrackingLineSearch::search(const std::function &phi, if (iter > max_iter) { std::cout << "Max iterations reached!\n"; - return alpha2; + // return alpha2; + + // return 0.0 indicating the linesearch failed to improve, + // so then Newton will terminate + return 0.0; } double alpha_tmp = 0.0; diff --git a/src/common/linesearch.hpp b/src/common/linesearch.hpp index 2a795cc0..54b6b60d 100644 --- a/src/common/linesearch.hpp +++ b/src/common/linesearch.hpp @@ -13,8 +13,7 @@ class LineSearch virtual double search(const std::function &phi, double phi0, double dphi0, - double alpha, - int max_iter = 10) = 0; + double alpha) = 0; virtual ~LineSearch() = default; }; @@ -25,13 +24,13 @@ class BacktrackingLineSearch : public LineSearch double search(const std::function &phi, double phi0, double dphi0, - double alpha, - int max_iter) override; + double alpha) override; double mu = 1e-4; double rho_hi = 0.9; double rho_lo = 0.1; int interp_order = 3; + int max_iter = 10; }; /// Functor class for \phi, the 1D function linesearch methods try to minimize diff --git a/src/common/relaxed_newton.cpp b/src/common/relaxed_newton.cpp index 8b0c5e66..1ddb1b39 100644 --- a/src/common/relaxed_newton.cpp +++ b/src/common/relaxed_newton.cpp @@ -25,6 +25,7 @@ std::unique_ptr createLineSearch( ls->rho_hi = options.value("rhohi", ls->rho_hi); ls->rho_lo = options.value("rholo", ls->rho_lo); ls->interp_order = options.value("interp-order", ls->interp_order); + ls->max_iter = options.value("maxiter", ls->max_iter); } return ls; } From ba763fd91a93df88a788f242f45ff76ca3c55b8e Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:15:02 -0500 Subject: [PATCH 59/72] added implementation of sensitivities for the actual IEAggregateFuncitonal output, and included tests for it --- src/physics/common_outputs.cpp | 37 +++++- src/physics/common_outputs.hpp | 13 +++ test/unit/test_common_outputs.cpp | 181 ++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 1 deletion(-) diff --git a/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index f90ec4a2..ebf55f33 100644 --- a/src/physics/common_outputs.cpp +++ b/src/physics/common_outputs.cpp @@ -127,13 +127,48 @@ AverageMagnitudeCurlState::AverageMagnitudeCurlState( } } +double jacobianVectorProduct(IEAggregateFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt) +{ + const MachInputs &inputs = *output.inputs; + double num = calcOutput(output.numerator, inputs); + double denom = calcOutput(output.denominator, inputs); + + auto out_dot = denom * jacobianVectorProduct(output.numerator, wrt_dot, wrt); + out_dot -= num * jacobianVectorProduct(output.denominator, wrt_dot, wrt); + out_dot /= pow(denom, 2); + return out_dot; +} + +void vectorJacobianProduct(IEAggregateFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + const MachInputs &inputs = *output.inputs; + double num = calcOutput(output.numerator, inputs); + double denom = calcOutput(output.denominator, inputs); + + output.scratch.SetSize(wrt_bar.Size()); + + output.scratch = 0.0; + vectorJacobianProduct(output.numerator, out_bar, wrt, output.scratch); + wrt_bar.Add(1 / denom, output.scratch); + + output.scratch = 0.0; + vectorJacobianProduct(output.denominator, out_bar, wrt, output.scratch); + wrt_bar.Add(-num / pow(denom, 2), output.scratch); +} + IEAggregateFunctional::IEAggregateFunctional( mfem::ParFiniteElementSpace &fes, std::map &fields, const nlohmann::json &options) : numerator(fes, fields), denominator(fes, fields) { - auto rho = options["rho"].get(); + // auto rho = options["rho"].get(); + auto rho = options.value("rho", 1.0); auto state_name = options.value("state", "state"); if (options.contains("attributes")) diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index 4e96ff9b..dc99c256 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -223,6 +223,7 @@ class IEAggregateFunctional friend void setInputs(IEAggregateFunctional &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.numerator, inputs); setInputs(output.denominator, inputs); } @@ -240,6 +241,16 @@ class IEAggregateFunctional return num / denom; } + friend double jacobianVectorProduct( + IEAggregateFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); + + friend void vectorJacobianProduct(IEAggregateFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar); + IEAggregateFunctional(mfem::ParFiniteElementSpace &fes, std::map &fields, const nlohmann::json &options); @@ -247,6 +258,8 @@ class IEAggregateFunctional private: FunctionalOutput numerator; FunctionalOutput denominator; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; class IECurlMagnitudeAggregateFunctional diff --git a/test/unit/test_common_outputs.cpp b/test/unit/test_common_outputs.cpp index ad6e148c..2eab6e8e 100644 --- a/test/unit/test_common_outputs.cpp +++ b/test/unit/test_common_outputs.cpp @@ -515,6 +515,187 @@ TEST_CASE("IEAggregateFunctional::calcOutput") REQUIRE(max_state == Approx(0.8544376503)); } +TEST_CASE("IEAggregateFunctional sensitivity wrt state") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &state = fields.at("state"); + mfem::Vector state_tv(state.space().GetTrueVSize()); + state.project(randState, state_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + auto fun_opts = R"({ + "rho": 10 + })"_json; + mach::IEAggregateFunctional fun(state.space(), fields, fun_opts); + mach::MachInputs inputs{ + {"state", state_tv}, + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the state + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + state.project(randState, pert_vec); + state.distributeSharedDofs(state_tv); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "state"); + + mfem::Vector wrt_bar(state.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(fun, adjoint_vec, "state", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + + + // now compute the finite-difference approximation... + state_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); + + state_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + state_tv.Add(delta, pert_vec); // remember to reset the state + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("IEAggregateFunctional sensitivity wrt mesh_coords") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + + auto &state = fields.at("state"); + mfem::Vector state_tv(state.space().GetTrueVSize()); + state.project(randState, state_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + auto fun_opts = R"({ + "rho": 10 + })"_json; + mach::IEAggregateFunctional fun(state.space(), fields, fun_opts); + mach::MachInputs inputs{ + {"state", state_tv}, + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb the mesh nodes + VectorFunctionCoefficient v_pert(dim, randVectorState); + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + mesh_coords.project(v_pert, pert_vec); + mesh_coords.distributeSharedDofs(mesh_coords_tv); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "mesh_coords"); + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(fun, adjoint_vec, "mesh_coords", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); + + mesh_coords_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + mesh_coords_tv.Add(delta, pert_vec); // remember to reset the mesh nodes + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + TEST_CASE("IECurlMagnitudeAggregateFunctional::calcOutput") { auto smesh = mfem::Mesh::MakeCartesian3D(3, 3, 3, mfem::Element::TETRAHEDRON); From ced8ffdfb6fc70c649392f34f2a3e59e19ba1835 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:19:24 -0500 Subject: [PATCH 60/72] added post-adjoint solve hook to do any sort of finalization steps. Removed try-catch from AbstractSolvers calcOutput and output sensitivities methods. Previously they would catch and print the what of any exception thrown, now they will not intercept. This is to allow a NotImplementedException to be thrown further up the chain --- src/common/abstract_solver.cpp | 75 ++++++++++--------- src/common/mach_residual.hpp | 38 ++++++++++ .../magnetostatic_residual.cpp | 16 +++- .../magnetostatic_residual.hpp | 6 ++ src/physics/mach_nonlinearform.cpp | 70 ++++++++++++++--- src/physics/mach_nonlinearform.hpp | 8 ++ 6 files changed, 165 insertions(+), 48 deletions(-) diff --git a/src/common/abstract_solver.cpp b/src/common/abstract_solver.cpp index 57c5e1ee..1ab46f49 100644 --- a/src/common/abstract_solver.cpp +++ b/src/common/abstract_solver.cpp @@ -1,4 +1,5 @@ #include "default_options.hpp" +#include "mach_residual.hpp" #include "mfem_extensions.hpp" #include "utils.hpp" @@ -164,6 +165,8 @@ void AbstractSolver2::solveForAdjoint(const MachInputs &inputs, adj_solver->Mult(work, adjoint); + finalizeAdjointSystem(*spatial_res, *adj_solver, inputs, work, adjoint); + /// log final state for (auto &[logger, options] : loggers) { @@ -378,28 +381,28 @@ void AbstractSolver2::outputJacobianVectorProduct(const std::string &of, const std::string &wrt, mfem::Vector &out_dot) { - try + // try + // { + auto output_iter = outputs.find(of); + if (output_iter == outputs.end()) { - auto output_iter = outputs.find(of); - if (output_iter == outputs.end()) - { - throw MachException("Did not find " + of + " in output map!\n"); - } - auto &output = output_iter->second; - setInputs(output, inputs); - if (out_dot.Size() == 1) - { - out_dot(0) += mach::jacobianVectorProduct(output, wrt_dot, wrt); - } - else - { - mach::jacobianVectorProduct(output, wrt_dot, wrt, out_dot); - } + throw MachException("Did not find " + of + " in output map!\n"); } - catch (const std::out_of_range &exception) + auto &output = output_iter->second; + setInputs(output, inputs); + if (out_dot.Size() == 1) { - std::cerr << exception.what() << std::endl; + out_dot(0) += mach::jacobianVectorProduct(output, wrt_dot, wrt); + } + else + { + mach::jacobianVectorProduct(output, wrt_dot, wrt, out_dot); } + // } + // catch (const std::out_of_range &exception) + // { + // std::cerr << exception.what() << std::endl; + // } } void AbstractSolver2::outputVectorJacobianProduct(const std::string &of, @@ -408,28 +411,28 @@ void AbstractSolver2::outputVectorJacobianProduct(const std::string &of, const std::string &wrt, mfem::Vector &wrt_bar) { - try + // try + // { + auto output_iter = outputs.find(of); + if (output_iter == outputs.end()) { - auto output_iter = outputs.find(of); - if (output_iter == outputs.end()) - { - throw MachException("Did not find " + of + " in output map!\n"); - } - auto &output = output_iter->second; - setInputs(output, inputs); - if (wrt_bar.Size() == 1) - { - wrt_bar(0) += mach::vectorJacobianProduct(output, out_bar, wrt); - } - else - { - mach::vectorJacobianProduct(output, out_bar, wrt, wrt_bar); - } + throw MachException("Did not find " + of + " in output map!\n"); } - catch (const std::out_of_range &exception) + auto &output = output_iter->second; + setInputs(output, inputs); + if (wrt_bar.Size() == 1) { - std::cerr << exception.what() << std::endl; + wrt_bar(0) += mach::vectorJacobianProduct(output, out_bar, wrt); + } + else + { + mach::vectorJacobianProduct(output, out_bar, wrt, wrt_bar); } + // } + // catch (const std::out_of_range &exception) + // { + // std::cerr << exception.what() << std::endl; + // } } void AbstractSolver2::linearize(const MachInputs &inputs) diff --git a/src/common/mach_residual.hpp b/src/common/mach_residual.hpp index 208761ca..2e584204 100644 --- a/src/common/mach_residual.hpp +++ b/src/common/mach_residual.hpp @@ -42,6 +42,17 @@ void setUpAdjointSystem(T & /*unused*/, "not specialized for concrete residual type!\n"); } +template +void finalizeAdjointSystem(T & /*unused*/, + mfem::Solver & /*unused*/, + const MachInputs & /*unused*/, + mfem::Vector & /*unused*/, + mfem::Vector & /*unused*/) +{ + throw NotImplementedException( + "not specialized for concrete residual type!\n"); +} + template double jacobianVectorProduct(T & /*unused*/, const mfem::Vector & /*unused*/, @@ -209,6 +220,12 @@ class MachResidual final : public mfem::Operator mfem::Vector &state_bar, mfem::Vector &adjoint); + friend void finalizeAdjointSystem(MachResidual &residual, + mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint); + /// Compute the residual's sensitivity to a scalar and contract it with /// wrt_dot /// \param[inout] residual - the residual whose sensitivity we want @@ -341,6 +358,10 @@ class MachResidual final : public mfem::Operator const MachInputs &inputs, mfem::Vector &state_bar, mfem::Vector &adjoint) = 0; + virtual void finalizeAdjointSystem_(mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) = 0; virtual double jacobianVectorProduct_(const mfem::Vector &wrt_dot, const std::string &wrt) = 0; virtual void jacobianVectorProduct_(const mfem::Vector &wrt_dot, @@ -404,6 +425,13 @@ class MachResidual final : public mfem::Operator { setUpAdjointSystem(data_, adj_solver, inputs, state_bar, adjoint); } + void finalizeAdjointSystem_(mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) override + { + finalizeAdjointSystem(data_, adj_solver, inputs, state_bar, adjoint); + } double jacobianVectorProduct_(const mfem::Vector &wrt_dot, const std::string &wrt) override { @@ -549,6 +577,16 @@ inline void setUpAdjointSystem(MachResidual &residual, residual.self_->setUpAdjointSystem_(adj_solver, inputs, state_bar, adjoint); } +inline void finalizeAdjointSystem(MachResidual &residual, + mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) +{ + residual.self_->finalizeAdjointSystem_( + adj_solver, inputs, state_bar, adjoint); +} + inline double jacobianVectorProduct(MachResidual &residual, const mfem::Vector &wrt_dot, const std::string &wrt) diff --git a/src/physics/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 55304f2c..5efb8760 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -65,7 +65,7 @@ void setInputs(MagnetostaticResidual &residual, const mach::MachInputs &inputs) { setInputs(residual.res, inputs); setInputs(*residual.load, inputs); - if (residual.current_coeff) + if (residual.current_coeff != nullptr) { setInputs(*residual.current_coeff, inputs); } @@ -83,6 +83,10 @@ void evaluate(MagnetostaticResidual &residual, { evaluate(residual.res, inputs, res_vec); setInputs(*residual.load, inputs); + if (residual.current_coeff != nullptr) + { + setInputs(*residual.current_coeff, inputs); + } addLoad(*residual.load, res_vec); // mfem::Vector state; @@ -122,6 +126,15 @@ void setUpAdjointSystem(MagnetostaticResidual &residual, setUpAdjointSystem(residual.res, adj_solver, inputs, state_bar, adjoint); } +void finalizeAdjointSystem(MagnetostaticResidual &residual, + mfem::Solver &adj_solver, + const mach::MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) +{ + finalizeAdjointSystem(residual.res, adj_solver, inputs, state_bar, adjoint); +} + double jacobianVectorProduct(MagnetostaticResidual &residual, const mfem::Vector &wrt_dot, const std::string &wrt) @@ -265,6 +278,7 @@ MagnetostaticResidual::MagnetostaticResidual( throw MachException( "Invalid mesh dimension for Magnetostatic Residual!\n"); } + setOptions(*this, options); } } // namespace mach diff --git a/src/physics/electromagnetics/magnetostatic_residual.hpp b/src/physics/electromagnetics/magnetostatic_residual.hpp index b96e8648..14e8d98c 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.hpp +++ b/src/physics/electromagnetics/magnetostatic_residual.hpp @@ -51,6 +51,12 @@ class MagnetostaticResidual final mfem::Vector &state_bar, mfem::Vector &adjoint); + friend void finalizeAdjointSystem(MagnetostaticResidual &residual, + mfem::Solver &adj_solver, + const mach::MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint); + friend double jacobianVectorProduct(MagnetostaticResidual &residual, const mfem::Vector &wrt_dot, const std::string &wrt); diff --git a/src/physics/mach_nonlinearform.cpp b/src/physics/mach_nonlinearform.cpp index f892b032..c3c73845 100644 --- a/src/physics/mach_nonlinearform.cpp +++ b/src/physics/mach_nonlinearform.cpp @@ -71,15 +71,24 @@ void evaluate(MachNonlinearForm &form, void linearize(MachNonlinearForm &form, const MachInputs &inputs) { + std::cout << "In linearize!\n"; setInputs(form, inputs); - // getJacobianTranspose also gets the regular Jacobian - getJacobianTranspose(form, inputs, "state"); + if (form.jac.Ptr() == nullptr) + { + getJacobian(form, inputs, "state"); + } + if (form.jac_trans == nullptr) + { + getJacobianTranspose(form, inputs, "state"); + } } mfem::Operator &getJacobian(MachNonlinearForm &form, const MachInputs &inputs, const std::string &wrt) { + std::cout << "Re-assembling Jacobian!\n"; + mfem::Vector state; setVectorFromInputs(inputs, "state", state, false, true); @@ -101,6 +110,9 @@ mfem::Operator &getJacobian(MachNonlinearForm &form, // reset our essential BCs to what they used to be form.nf.SetEssentialTrueDofs(ess_tdof_list); + // reset transposed Jacobian to null (to indicate we should re-transpose it) + form.jac_trans = nullptr; + return *form.jac; } @@ -108,17 +120,21 @@ mfem::Operator &getJacobianTranspose(MachNonlinearForm &form, const MachInputs &inputs, const std::string &wrt) { - getJacobian(form, inputs, wrt); - - auto *hypre_jac = form.jac.As(); - if (hypre_jac == nullptr) + if (form.jac_trans == nullptr) { - throw MachException( - "getJacobianTranspose (MachNonlinearForm) only supports " - "Jacobian matrices assembled to a HypreParMatrix!\n"); - } + std::cout << "Re-transposing Jacobian!\n"; + // getJacobian(form, inputs, wrt); + + auto *hypre_jac = form.jac.As(); + if (hypre_jac == nullptr) + { + throw MachException( + "getJacobianTranspose (MachNonlinearForm) only supports " + "Jacobian matrices assembled to a HypreParMatrix!\n"); + } - form.jac_trans = std::unique_ptr(hypre_jac->Transpose()); + form.jac_trans = std::unique_ptr(hypre_jac->Transpose()); + } return *form.jac_trans; } @@ -128,6 +144,8 @@ void setUpAdjointSystem(MachNonlinearForm &form, mfem::Vector &state_bar, mfem::Vector &adjoint) { + std::cout << "Setting up adjoint system!\n"; + auto &jac_trans = getJacobianTranspose(form, inputs, "state"); adj_solver.SetOperator(jac_trans); @@ -136,6 +154,7 @@ void setUpAdjointSystem(MachNonlinearForm &form, { return; } + adj_solver.Mult(state_bar, adjoint); auto *hypre_jac_e = form.jac_e.As(); @@ -146,6 +165,35 @@ void setUpAdjointSystem(MachNonlinearForm &form, "Jacobian matrices assembled to a HypreParMatrix!\n"); } hypre_jac_e->MultTranspose(-1.0, adjoint, 1.0, state_bar); + + // state_bar.GetSubVector(ess_tdof_list, form.scratch); + // state_bar.SetSubVector(ess_tdof_list, 0.0); + + // form.adj_work1 = state_bar; + // state_bar.SetSubVector(ess_tdof_list, 0.0); + // form.adj_work2 = state_bar; +} + +void finalizeAdjointSystem(MachNonlinearForm &form, + mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) +{ + const auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); + if (ess_tdof_list.Size() == 0) + { + return; + } + + // adjoint.SetSubVector(ess_tdof_list, form.scratch); + + // mfem::Vector diff = form.adj_work2; + // diff -= form.adj_work1; + // adjoint -= diff; + + // std::cout << "adjoint:\n"; + // adjoint.Print(mfem::out, 1); } double jacobianVectorProduct(MachNonlinearForm &form, diff --git a/src/physics/mach_nonlinearform.hpp b/src/physics/mach_nonlinearform.hpp index 76c96e66..4ed04853 100644 --- a/src/physics/mach_nonlinearform.hpp +++ b/src/physics/mach_nonlinearform.hpp @@ -51,6 +51,12 @@ class MachNonlinearForm final mfem::Vector &state_bar, mfem::Vector &adjoint); + friend void finalizeAdjointSystem(MachNonlinearForm &form, + mfem::Solver &adj_solver, + const MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint); + friend double jacobianVectorProduct(MachNonlinearForm &form, const mfem::Vector &wrt_dot, const std::string &wrt); @@ -164,6 +170,8 @@ class MachNonlinearForm final /// Holds the transpose of the Jacobian, needed for solving for the adjoint std::unique_ptr jac_trans; + + mfem::Vector adj_work1, adj_work2; }; template From b7bd248275491822924e661226a2f83400310d74 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:25:34 -0500 Subject: [PATCH 61/72] minor changes related to OpenMDAO integration --- mach/mach_output.py | 37 ++++++-- mach/mach_state.py | 158 +++++++++++++++++++++---------- src/common/functional_output.cpp | 22 ++++- 3 files changed, 156 insertions(+), 61 deletions(-) diff --git a/mach/mach_output.py b/mach/mach_output.py index 757533fa..51b7ec10 100644 --- a/mach/mach_output.py +++ b/mach/mach_output.py @@ -114,6 +114,7 @@ def compute(self, inputs, outputs): def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): solver = self.options["solver"] func = self.options["func"] + # print(f"Calling compute_jacvec_product for func {func} with inputs: {inputs}, d_inputs: {d_inputs}, d_outputs: {d_outputs}, and mode: {mode}") # Copy vector inputs into internal contiguous data buffers for input in inputs: @@ -125,17 +126,28 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): input_dict = dict(zip(inputs.keys(), inputs.values())) input_dict.update(self.vectors) - for input in inputs: - print(f"d_inputs[{input}]: {d_inputs[input]}") + # for input in inputs: + # print(f"inputs[{input}] stride: {inputs[input].strides}") + + # for input in inputs: + # if input in d_inputs: + # print(f"d_inputs[{input}]: {d_inputs[input]}") + # print(f"d_inputs[{input}] stride: {d_inputs[input].strides}") + + # print(f"d_outputs[{func}] stride: {d_outputs[func].strides}") try: if mode == 'fwd': if func in d_outputs: + # print(f"func {func} is in d_outputs") for input in inputs: if input in d_inputs: + # print(f"input {input} is in d_inputs") + # print(f"") + func_dot = np.zeros_like(d_outputs[func]) func_dot = np.zeros_like(d_outputs[func]) - print(f"wrt_dot for input {input}: {d_inputs[input]}") + # print(f"wrt_dot for input {input}: {d_inputs[input]} for fun {func}") solver.outputJacobianVectorProduct(of=func, inputs=input_dict, wrt_dot=d_inputs[input], @@ -152,6 +164,7 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): # Recommended to make sure your code can run without MPI too, for testing. d_outputs[func] += func_dot + # print(f"out_dot for func {func}: {d_outputs[func]}") elif mode == 'rev': if func in d_outputs: for input in inputs: @@ -170,10 +183,22 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): out_bar=func_bar, wrt=input, wrt_bar=d_inputs[input]) - except NotImplementedError as err: - if self.options["check_partials"]: - pass + except Exception as err: + if isinstance(err, NotImplementedError): + if self.options["check_partials"]: + print(f"\n\nNot implemented error passed!!!\n\n") + pass + else: + print(f"\n\nNot implemented error raised!!!\n\n") + raise err else: + print("\n\ngeneric exception!!!\n\n") raise err + # except NotImplementedError as err: + # if self.options["check_partials"]: + # pass + # else: + # raise err + diff --git a/mach/mach_state.py b/mach/mach_state.py index f9e92b78..e91099ba 100644 --- a/mach/mach_state.py +++ b/mach/mach_state.py @@ -3,6 +3,7 @@ from .pyMach import PDESolver + def _getMeshCoordsName(solver_options): type = solver_options["solver-type"]["type"] @@ -15,16 +16,22 @@ def _getMeshCoordsName(solver_options): suffix = "conduct" else: raise RuntimeError("Bad physics given to MachSolver!") - + return "x_" + suffix + "0" + class MachMesh(om.IndepVarComp): """ Component to read the initial mesh coordinates """ def initialize(self): - self.options.declare("solver", types=PDESolver, desc="the mach solver object itself", recordable=False) + self.options.declare( + "solver", + types=PDESolver, + desc="the mach solver object itself", + recordable=False, + ) def setup(self): solver = self.options["solver"] @@ -36,14 +43,25 @@ def setup(self): solver_options = solver.getOptions() mesh_name = _getMeshCoordsName(solver_options) - self.add_output(mesh_name, distributed=True, val=mesh_coords, - desc="mesh node coordinates", tags=["mphys_coordinates"]) + self.add_output( + mesh_name, + distributed=True, + val=mesh_coords, + desc="mesh node coordinates", + tags=["mphys_coordinates"], + ) + class MachState(om.ImplicitComponent): """OpenMDAO component that converges the state variables""" def initialize(self): - self.options.declare("solver", types=PDESolver, desc="the mach solver object itself", recordable=False) + self.options.declare( + "solver", + types=PDESolver, + desc="the mach solver object itself", + recordable=False, + ) self.options.declare("depends", types=list) self.options.declare("check_partials", default=False) @@ -59,25 +77,23 @@ def setup(self): for input in self.options["depends"]: if input == "mesh_coords": mesh_size = solver.getFieldSize(input) - self.add_input("mesh_coords", - # distributed=True, - shape=mesh_size, - # shape_by_conn=True, - desc="volume mesh node coordinates", - tags=["mphys_coordinates"]) + self.add_input( + "mesh_coords", + # distributed=True, + shape=mesh_size, + # shape_by_conn=True, + desc="volume mesh node coordinates", + tags=["mphys_coordinates"], + ) self.vectors["mesh_coords"] = np.empty(0) else: input_size = solver.getFieldSize(input) if input_size == 0: input_size = 1 if ext_fields and input in solver_options["external-fields"]: - self.add_input(input, - shape=input_size, - tags=["mphys_coupling"]) + self.add_input(input, shape=input_size, tags=["mphys_coupling"]) else: - self.add_input(input, - shape=input_size, - tags=["mphys_input"]) + self.add_input(input, shape=input_size, tags=["mphys_input"]) if input_size > 1: self.vectors[input] = np.empty(input_size) @@ -92,11 +108,13 @@ def setup(self): # state outputs local_state_size = solver.getStateSize() state = np.zeros(local_state_size) - self.add_output("state", - val=state, - distributed=True, - desc="Mach state vector", - tags=["mphys_coupling"]) + self.add_output( + "state", + val=state, + distributed=True, + desc="Mach state vector", + tags=["mphys_coupling"], + ) self.vectors["state"] = state self.vectors["state_res"] = np.empty_like(state) @@ -119,7 +137,7 @@ def apply_nonlinear(self, inputs, outputs, residuals): input_dict.update(dict(zip(outputs.keys(), outputs.values()))) input_dict.update(self.vectors) - residual = self.vectors["state_res"] + residual = self.vectors["state_res"] solver.calcResidual(input_dict, residual) residuals["state"][:] = residual[:] @@ -140,7 +158,7 @@ def solve_nonlinear(self, inputs, outputs): input_dict = dict(zip(inputs.keys(), inputs.values())) input_dict.update(dict(zip(outputs.keys(), outputs.values()))) - input_dict.update(self.vectors) + input_dict.update(self.vectors) state = self.vectors["state"] solver.solveForState(input_dict, state) @@ -162,7 +180,7 @@ def linearize(self, inputs, outputs, residuals): input_dict = dict(zip(inputs.keys(), inputs.values())) input_dict.update(dict(zip(outputs.keys(), outputs.values()))) - input_dict.update(self.vectors) + input_dict.update(self.vectors) self.linear_inputs = input_dict solver = self.options["solver"] @@ -173,34 +191,65 @@ def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode): try: if mode == "fwd": - if "state" in d_residuals: - if "state" in d_outputs: - solver.jacobianVectorProduct(wrt_dot=d_outputs["state"], - wrt="state", - res_dot=d_residuals["state"]) + if "state" in d_residuals: + if "state" in d_outputs: + if np.linalg.norm(d_outputs["state"], 2) != 0.0: + solver.jacobianVectorProduct( + wrt_dot=d_outputs["state"], + wrt="state", + res_dot=d_residuals["state"], + ) + else: + print("zero wrt_dot!") for input in d_inputs: - solver.jacobianVectorProduct(wrt_dot=d_inputs[input], - wrt=input, - res_dot=d_residuals["state"]) + if np.linalg.norm(d_inputs[input], 2) != 0.0: + solver.jacobianVectorProduct( + wrt_dot=d_inputs[input], + wrt=input, + res_dot=d_residuals["state"], + ) + else: + print("zero wrt_dot!") elif mode == "rev": - if "state" in d_residuals: - if "state" in d_outputs: - solver.vectorJacobianProduct(res_bar=d_residuals["state"], - wrt="state", - wrt_bar=d_outputs["state"]) - - for input in d_inputs: - solver.vectorJacobianProduct(res_bar=d_residuals["state"], - wrt=input, - wrt_bar=d_inputs[input]) - except NotImplementedError as err: - if self.options["check_partials"]: - pass + if "state" in d_residuals: + if np.linalg.norm(d_residuals["state"], 2) != 0.0: + + if "state" in d_outputs: + solver.vectorJacobianProduct( + res_bar=d_residuals["state"], + wrt="state", + wrt_bar=d_outputs["state"], + ) + + for input in d_inputs: + solver.vectorJacobianProduct( + res_bar=d_residuals["state"], + wrt=input, + wrt_bar=d_inputs[input], + ) + else: + print("zero res_bar!") + + except Exception as err: + if isinstance(err, NotImplementedError): + if self.options["check_partials"]: + print(f"\n\nNot implemented error passed!!!\n\n") + pass + else: + print(f"\n\nNot implemented error raised!!!\n\n") + raise err else: + print("\n\ngeneric exception!!!\n\n") raise err + # except NotImplementedError as err: + # if self.options["check_partials"]: + # pass + # else: + # raise err + def solve_linear(self, d_outputs, d_residuals, mode): if mode == "fwd": if self.options["check_partials"]: @@ -209,8 +258,15 @@ def solve_linear(self, d_outputs, d_residuals, mode): raise NotImplementedError("forward mode requested but not implemented") if mode == "rev": - solver = self.options["solver"] - input_dict = self.linear_inputs - solver.solveForAdjoint(input_dict, - d_outputs["state"], - d_residuals["state"]) \ No newline at end of file + print("!!!!!!! Solving for adjoint !!!!!!!") + if np.linalg.norm(d_outputs["state"], 2) != 0.0: + solver = self.options["solver"] + input_dict = self.linear_inputs + solver.solveForAdjoint( + input_dict, d_outputs["state"], d_residuals["state"] + ) + # solver.solveForAdjoint(input_dict, + # state_bar, + # d_residuals["state"]) + else: + print("zero fun_bar!") diff --git a/src/common/functional_output.cpp b/src/common/functional_output.cpp index 5be8d9bb..da8d73e4 100644 --- a/src/common/functional_output.cpp +++ b/src/common/functional_output.cpp @@ -108,7 +108,15 @@ double jacobianVectorProduct(FunctionalOutput &output, { Vector state; output.func_fields->at("state").setTrueVec(state); - output.scratch(0) = output.output_scalar_sens.at(wrt).GetEnergy(state); + const auto &state_gf = output.func_fields->at("state").gridFunc(); + + auto wrt_key = wrt.substr(0, wrt.find(':')); + // output.scratch(0) = + // output.output_scalar_sens.at(wrt).GetEnergy(state); + // TODO: need to confirm what OpenMDAO expects in terms of parallel outputs: + // should we give it the already reduced value or not? + output.scratch(0) = + output.output_scalar_sens.at(wrt_key).GetEnergy(state_gf); } else { @@ -124,9 +132,15 @@ double vectorJacobianProduct(FunctionalOutput &output, const mfem::Vector &out_bar, const std::string &wrt) { - Vector state; - output.func_fields->at("state").setTrueVec(state); - double sens = output.output_scalar_sens.at(wrt).GetEnergy(state); + // Vector state; + // output.func_fields->at("state").setTrueVec(state); + const auto &state_gf = output.func_fields->at("state").gridFunc(); + + auto wrt_key = wrt.substr(0, wrt.find(':')); + // double sens = output.output_scalar_sens.at(wrt_key).GetEnergy(state); + // TODO: need to confirm what OpenMDAO expects in terms of parallel outputs: + // should we give it the already reduced value or not? + double sens = output.output_scalar_sens.at(wrt_key).GetEnergy(state_gf); return sens * out_bar(0); } From b5c70a41d62cdb42b2ccb1ceb445bb1e39b515a4 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:28:01 -0500 Subject: [PATCH 62/72] changed use of Adept for auto-diff in current and magnet source functions to be more efficient --- .../current_source_functions.cpp | 73 ++++----- .../magnetic_source_functions.cpp | 142 +++++++++++++----- 2 files changed, 141 insertions(+), 74 deletions(-) diff --git a/src/physics/electromagnetics/current_source_functions.cpp b/src/physics/electromagnetics/current_source_functions.cpp index e5ed6489..45e06f6e 100644 --- a/src/physics/electromagnetics/current_source_functions.cpp +++ b/src/physics/electromagnetics/current_source_functions.cpp @@ -559,27 +559,29 @@ double box1CurrentSource2D(const mfem::Vector &x) return box1_current2D(x.GetData()); } +/// TODO: this is how Adept should be used here, eventually +/// should update the rest of the use cases to match this +/// (it's more efficient) void box1CurrentSource2DRevDiff(adept::Stack &diff_stack, const mfem::Vector &x, - const double &J_bar, + double J_bar, mfem::Vector &x_bar) { - // mfem::DenseMatrix source_jac(3); - // // declare vectors of active input variables - // std::vector x_a(x.Size()); - // // copy data from mfem::Vector - // adept::set_values(x_a.data(), x.Size(), x.GetData()); - // // start recording - // diff_stack.new_recording(); - // // the depedent variable must be declared after the recording - // std::vector J_a(x.Size()); - // box1_current(x_a.data(), J_a.data()); - // // set the independent and dependent variable - // diff_stack.independent(x_a.data(), x.Size()); - // diff_stack.dependent(J_a.data(), x.Size()); - // // calculate the jacobian w.r.t state vaiables - // diff_stack.jacobian(source_jac.GetData()); - // source_jac.MultTranspose(V_bar, x_bar); + std::array x_a; + // copy data from mfem::Vector + adept::set_values(x_a.data(), x.Size(), x.GetData()); + + // start recording + diff_stack.new_recording(); + + // the depedent variable must be declared after the recording + auto J_a = box1_current2D(x_a.data()); + + J_a.set_gradient(J_bar); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), x.Size(), x_bar.GetData()); } double box2CurrentSource2D(const mfem::Vector &x) @@ -587,26 +589,29 @@ double box2CurrentSource2D(const mfem::Vector &x) return box2_current2D(x.GetData()); } +/// TODO: this is how Adept should be used here, eventually +/// should update the rest of the use cases to match this +/// (it's more efficient) void box2CurrentSource2DRevDiff(adept::Stack &diff_stack, const mfem::Vector &x, - const double &J_bar, + double J_bar, mfem::Vector &x_bar) { - // mfem::DenseMatrix source_jac(3); - // // declare vectors of active input variables - // std::vector x_a(x.Size()); - // // copy data from mfem::Vector - // adept::set_values(x_a.data(), x.Size(), x.GetData()); - // // start recording - // diff_stack.new_recording(); - // // the depedent variable must be declared after the recording - // adouble J_a = box2_current2D(x_a.data()); - // // set the independent and dependent variable - // diff_stack.independent(x_a.data(), x.Size()); - // diff_stack.dependent(J_a); - // // calculate the jacobian w.r.t state vaiables - // diff_stack.jacobian(source_jac.GetData()); - // source_jac.MultTranspose(V_bar, x_bar); + std::array x_a; + // copy data from mfem::Vector + adept::set_values(x_a.data(), x.Size(), x.GetData()); + + // start recording + diff_stack.new_recording(); + + // the depedent variable must be declared after the recording + auto J_a = box2_current2D(x_a.data()); + + J_a.set_gradient(J_bar); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), x.Size(), x_bar.GetData()); } void team13CurrentSource(const mfem::Vector &x, mfem::Vector &J) @@ -1038,7 +1043,7 @@ void CurrentDensityCoefficient2D::EvalRevDiff( const mfem::IntegrationPoint &ip, mfem::DenseMatrix &PointMat_bar) { - Q_bar *= 1.0; + Q_bar *= -1.0; current_coeff.EvalRevDiff(Q_bar, trans, ip, PointMat_bar); } diff --git a/src/physics/electromagnetics/magnetic_source_functions.cpp b/src/physics/electromagnetics/magnetic_source_functions.cpp index 0e7b515e..6e5a4e1d 100644 --- a/src/physics/electromagnetics/magnetic_source_functions.cpp +++ b/src/physics/electromagnetics/magnetic_source_functions.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -147,22 +148,39 @@ void northMagnetizationSourceRevDiff(adept::Stack &diff_stack, const mfem::Vector &V_bar, mfem::Vector &x_bar) { - mfem::DenseMatrix source_jac(3); + // mfem::DenseMatrix source_jac(3); + // // declare vectors of active input variables + // std::vector x_a(x.Size()); + // // copy data from mfem::Vector + // adept::set_values(x_a.data(), x.Size(), x.GetData()); + // // start recording + // diff_stack.new_recording(); + // // the depedent variable must be declared after the recording + // std::vector M_a(x.Size()); + // north_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(M_a.data(), x.Size()); + // // calculate the jacobian w.r.t position + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); // declare vectors of active input variables - std::vector x_a(x.Size()); + + std::array x_a; // copy data from mfem::Vector - adept::set_values(x_a.data(), x.Size(), x.GetData()); + adept::set_values(x_a.data(), vdim, x.GetData()); // start recording diff_stack.new_recording(); + // the depedent variable must be declared after the recording - std::vector M_a(x.Size()); + std::array M_a; north_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); - // set the independent and dependent variable - diff_stack.independent(x_a.data(), x.Size()); - diff_stack.dependent(M_a.data(), x.Size()); - // calculate the jacobian w.r.t position - diff_stack.jacobian(source_jac.GetData()); - source_jac.MultTranspose(V_bar, x_bar); + + adept::set_gradients(M_a.data(), vdim, V_bar.GetData()); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), vdim, x_bar.GetData()); } /// function describing permanent magnet magnetization pointing inwards @@ -187,22 +205,39 @@ void southMagnetizationSourceRevDiff(adept::Stack &diff_stack, const mfem::Vector &V_bar, mfem::Vector &x_bar) { - mfem::DenseMatrix source_jac(3); + // mfem::DenseMatrix source_jac(3); + // // declare vectors of active input variables + // std::vector x_a(x.Size()); + // // copy data from mfem::Vector + // adept::set_values(x_a.data(), x.Size(), x.GetData()); + // // start recording + // diff_stack.new_recording(); + // // the depedent variable must be declared after the recording + // std::vector M_a(x.Size()); + // south_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(M_a.data(), x.Size()); + // // calculate the jacobian w.r.t position + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); + // declare vectors of active input variables - std::vector x_a(x.Size()); + std::array x_a; // copy data from mfem::Vector - adept::set_values(x_a.data(), x.Size(), x.GetData()); + adept::set_values(x_a.data(), vdim, x.GetData()); // start recording diff_stack.new_recording(); + // the depedent variable must be declared after the recording - std::vector M_a(x.Size()); + std::array M_a; south_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); - // set the independent and dependent variable - diff_stack.independent(x_a.data(), x.Size()); - diff_stack.dependent(M_a.data(), x.Size()); - // calculate the jacobian w.r.t position - diff_stack.jacobian(source_jac.GetData()); - source_jac.MultTranspose(V_bar, x_bar); + + adept::set_gradients(M_a.data(), vdim, V_bar.GetData()); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), vdim, x_bar.GetData()); } /// function describing permanent magnet magnetization pointing inwards @@ -227,22 +262,39 @@ void cwMagnetizationSourceRevDiff(adept::Stack &diff_stack, const mfem::Vector &V_bar, mfem::Vector &x_bar) { - mfem::DenseMatrix source_jac(3); + // mfem::DenseMatrix source_jac(3); + // // declare vectors of active input variables + // std::vector x_a(x.Size()); + // // copy data from mfem::Vector + // adept::set_values(x_a.data(), x.Size(), x.GetData()); + // // start recording + // diff_stack.new_recording(); + // // the depedent variable must be declared after the recording + // std::vector M_a(x.Size()); + // cw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(M_a.data(), x.Size()); + // // calculate the jacobian w.r.t position + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); // declare vectors of active input variables - std::vector x_a(x.Size()); + + std::array x_a; // copy data from mfem::Vector - adept::set_values(x_a.data(), x.Size(), x.GetData()); + adept::set_values(x_a.data(), vdim, x.GetData()); // start recording diff_stack.new_recording(); + // the depedent variable must be declared after the recording - std::vector M_a(x.Size()); + std::array M_a; cw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); - // set the independent and dependent variable - diff_stack.independent(x_a.data(), x.Size()); - diff_stack.dependent(M_a.data(), x.Size()); - // calculate the jacobian w.r.t position - diff_stack.jacobian(source_jac.GetData()); - source_jac.MultTranspose(V_bar, x_bar); + + adept::set_gradients(M_a.data(), vdim, V_bar.GetData()); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), vdim, x_bar.GetData()); } /// function describing permanent magnet magnetization pointing inwards @@ -267,22 +319,32 @@ void ccwMagnetizationSourceRevDiff(adept::Stack &diff_stack, const mfem::Vector &V_bar, mfem::Vector &x_bar) { - mfem::DenseMatrix source_jac(3); + // std::array source_jac_buffer; + // mfem::DenseMatrix source_jac(source_jac_buffer.data(), vdim, vdim); + // declare vectors of active input variables - std::vector x_a(x.Size()); + std::array x_a; // copy data from mfem::Vector - adept::set_values(x_a.data(), x.Size(), x.GetData()); + adept::set_values(x_a.data(), vdim, x.GetData()); // start recording diff_stack.new_recording(); + // the depedent variable must be declared after the recording - std::vector M_a(x.Size()); + std::array M_a; ccw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); - // set the independent and dependent variable - diff_stack.independent(x_a.data(), x.Size()); - diff_stack.dependent(M_a.data(), x.Size()); - // calculate the jacobian w.r.t position - diff_stack.jacobian(source_jac.GetData()); - source_jac.MultTranspose(V_bar, x_bar); + + adept::set_gradients(M_a.data(), vdim, V_bar.GetData()); + diff_stack.compute_adjoint(); + + // calculate the vector jacobian product w.r.t position + adept::get_gradients(x_a.data(), vdim, x_bar.GetData()); + + // // set the independent and dependent variable + // diff_stack.independent(x_a.data(), x.Size()); + // diff_stack.dependent(M_a.data(), x.Size()); + // // calculate the jacobian w.r.t position + // diff_stack.jacobian(source_jac.GetData()); + // source_jac.MultTranspose(V_bar, x_bar); } /// function defining magnetization aligned with the x axis From 2b0129e4dd8526f59961f3ace1d976e9682d8247 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:31:22 -0500 Subject: [PATCH 63/72] improving test coverage of EM solver related functionality. Specifically on output sensitivity verification --- .../electromagnetics/electromag_integ.cpp | 153 ++++- .../electromagnetics/electromag_integ.hpp | 55 +- .../electromagnetics/electromag_outputs.cpp | 85 +++ .../reluctivity_coefficient.cpp | 41 ++ test/regression/CMakeLists.txt | 1 + .../test_magnetostatic_residual.cpp | 617 ++++++++++++++++++ test/unit/CMakeLists.txt | 1 + test/unit/test_electromag_integ.cpp | 66 ++ test/unit/test_electromag_outputs.cpp | 112 ++++ test/unit/test_reluctivity_coeff.cpp | 163 +++++ 10 files changed, 1285 insertions(+), 9 deletions(-) create mode 100644 test/regression/test_magnetostatic_residual.cpp create mode 100644 test/unit/test_reluctivity_coeff.cpp diff --git a/src/physics/electromagnetics/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index bd83b77f..6d24952e 100644 --- a/src/physics/electromagnetics/electromag_integ.cpp +++ b/src/physics/electromagnetics/electromag_integ.cpp @@ -3671,21 +3671,87 @@ double ACLossFunctionalIntegrator::GetElementEnergy( return fun; } +// void ACLossFunctionalIntegrator::AssembleElementVector(const FiniteElement +// &el, +// ElementTransformation &trans, +// const Vector &elfun, +// Vector &elfun_bar) +// { +// /// number of degrees of freedom +// int ndof = el.GetDof(); + +// #ifdef MFEM_THREAD_SAFE +// Vector shape(ndof); +// #else +// shape.SetSize(ndof); +// #endif + +// const IntegrationRule *ir = IntRule; +// if (ir == nullptr) +// { +// int order = [&]() +// { +// if (el.Space() == FunctionSpace::Pk) +// { +// return 2 * el.GetOrder() - 2; +// } +// else +// { +// return 2 * el.GetOrder(); +// } +// }(); + +// ir = &IntRules.Get(el.GetGeomType(), order); +// } + +// elfun_bar.SetSize(ndof); +// elfun_bar = 0.0; +// for (int i = 0; i < ir->GetNPoints(); i++) +// { +// const IntegrationPoint &ip = ir->IntPoint(i); +// trans.SetIntPoint(&ip); + +// /// holds quadrature weight +// double trans_weight = trans.Weight(); +// const double w = ip.weight * trans_weight; + +// el.CalcPhysShape(trans, shape); +// const auto b_mag = shape * elfun; + +// const auto sigma_v = sigma.Eval(trans, ip); + +// const auto loss = sigma_v * pow(b_mag, 2); +// // fun += loss * w; + +// /// Start reverse pass... +// double fun_bar = 1.0; + +// /// fun += loss * w; +// double loss_bar = fun_bar * w; + +// /// const double loss = sigma_v * pow(b_mag, 2); +// double b_mag_bar = loss_bar * sigma_v * 2 * b_mag; + +// /// const double b_mag = shape * elfun; +// elfun_bar.Add(b_mag_bar, shape); +// } +// } + void ACLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( const mfem::FiniteElement &mesh_el, mfem::ElementTransformation &mesh_trans, mfem::Vector &mesh_coords_bar) { const int element = mesh_trans.ElementNo; - const auto &el = *state.FESpace()->GetFE(element); - auto &trans = *state.FESpace()->GetElementTransformation(element); + const auto &el = *peak_flux.FESpace()->GetFE(element); + auto &trans = *peak_flux.FESpace()->GetElementTransformation(element); const int ndof = el.GetDof(); const int mesh_ndof = mesh_el.GetDof(); const int space_dim = mesh_trans.GetSpaceDim(); - auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); - state.GetSubVector(vdofs, elfun); + auto *dof_tr = peak_flux.FESpace()->GetElementVDofs(element, vdofs); + peak_flux.GetSubVector(vdofs, elfun); if (dof_tr != nullptr) { dof_tr->InvTransformPrimal(elfun); @@ -3782,6 +3848,85 @@ void ACLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( } } +void ACLossFunctionalIntegratorPeakFluxSens::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elfun_bar) +{ + const int ndof = el.GetDof(); + +#ifdef MFEM_THREAD_SAFE + mfem::Vector elfun; +#endif + const int element = trans.ElementNo; + auto *dof_tr = peak_flux.FESpace()->GetElementVDofs(element, vdofs); + peak_flux.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + +#ifdef MFEM_THREAD_SAFE + mfem::Vector shape; +#else + auto &shape = integ.shape; +#endif + + shape.SetSize(ndof); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + auto &sigma = integ.sigma; + + elfun_bar.SetSize(ndof); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + /// holds quadrature weight + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; + + el.CalcPhysShape(trans, shape); + const auto b_mag = shape * elfun; + + const auto sigma_v = sigma.Eval(trans, ip); + + // const auto loss = sigma_v * pow(b_mag, 2); + // fun += loss * w; + + /// Start reverse pass... + double fun_bar = 1.0; + + /// fun += loss * w; + double loss_bar = fun_bar * w; + + /// const double loss = sigma_v * pow(b_mag, 2); + double b_mag_bar = loss_bar * sigma_v * 2 * b_mag; + + /// const double b_mag = shape * elfun; + elfun_bar.Add(b_mag_bar, shape); + } +} + void setInputs(ACLossFunctionalDistributionIntegrator &integ, const MachInputs &inputs) { diff --git a/src/physics/electromagnetics/electromag_integ.hpp b/src/physics/electromagnetics/electromag_integ.hpp index 1e2a8079..9de8cd77 100644 --- a/src/physics/electromagnetics/electromag_integ.hpp +++ b/src/physics/electromagnetics/electromag_integ.hpp @@ -1241,17 +1241,18 @@ class ACLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator mfem::Vector shape; #endif friend class ACLossFunctionalIntegratorMeshSens; + friend class ACLossFunctionalIntegratorPeakFluxSens; }; class ACLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator { public: - /// \param[in] state - the state grid function + /// \param[in] peak_flux - the peak_flux grid function /// \param[in] integ - reference to primal integrator that holds inputs for /// integrator - ACLossFunctionalIntegratorMeshSens(mfem::GridFunction &state, + ACLossFunctionalIntegratorMeshSens(mfem::GridFunction &peak_flux, ACLossFunctionalIntegrator &integ) - : state(state), integ(integ) + : peak_flux(peak_flux), integ(integ) { } /// \brief - assemble an element's contribution to dJdX @@ -1264,8 +1265,8 @@ class ACLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator mfem::Vector &mesh_coords_bar) override; private: - /// State GridFunction, needed to get integration order for each element - mfem::GridFunction &state; + /// peak_flux GridFunction + mfem::GridFunction &peak_flux; /// reference to primal integrator ACLossFunctionalIntegrator &integ; @@ -1277,6 +1278,38 @@ class ACLossFunctionalIntegratorMeshSens : public mfem::LinearFormIntegrator #endif }; +class ACLossFunctionalIntegratorPeakFluxSens : public mfem::LinearFormIntegrator +{ +public: + /// \param[in] peak_flux - the peak_flux grid function + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + ACLossFunctionalIntegratorPeakFluxSens(mfem::GridFunction &peak_flux, + ACLossFunctionalIntegrator &integ) + : peak_flux(peak_flux), integ(integ) + { } + + /// \brief - assemble an element's contribution to dJdX + /// \param[in] pf_el - the finite element to integrate over + /// \param[in] pf_trans - the transformation between reference and physical + /// space + /// \param[out] peak_flux_bar - dJdB for the element + void AssembleRHSElementVect(const mfem::FiniteElement &pf_el, + mfem::ElementTransformation &pf_trans, + mfem::Vector &peak_flux_bar) override; + +private: + /// peak_flux GridFunction + mfem::GridFunction &peak_flux; + /// reference to primal integrator + ACLossFunctionalIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector elfun; +#endif +}; + inline void addDomainSensitivityIntegrator( ACLossFunctionalIntegrator &primal_integ, std::map &fields, @@ -1287,11 +1320,18 @@ inline void addDomainSensitivityIntegrator( auto &mesh_fes = fields.at("mesh_coords").space(); output_sens.emplace("mesh_coords", &mesh_fes); + auto &peak_flux_fes = fields.at("peak_flux").space(); + output_sens.emplace("peak_flux", &peak_flux_fes); + if (attr_marker == nullptr) { output_sens.at("mesh_coords") .AddDomainIntegrator(new ACLossFunctionalIntegratorMeshSens( fields.at("peak_flux").gridFunc(), primal_integ)); + + output_sens.at("peak_flux") + .AddDomainIntegrator(new ACLossFunctionalIntegratorPeakFluxSens( + fields.at("peak_flux").gridFunc(), primal_integ)); } else { @@ -1300,6 +1340,11 @@ inline void addDomainSensitivityIntegrator( new ACLossFunctionalIntegratorMeshSens( fields.at("peak_flux").gridFunc(), primal_integ), *attr_marker); + output_sens.at("peak_flux") + .AddDomainIntegrator( + new ACLossFunctionalIntegratorPeakFluxSens( + fields.at("peak_flux").gridFunc(), primal_integ), + *attr_marker); } } diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 2bc40d70..1487e1f2 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -697,6 +697,30 @@ double jacobianVectorProduct(ACLossFunctional &output, return loss_dot / volume - loss / pow(volume, 2) * volume_dot; } + else if (wrt.rfind("peak_flux", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + double sigma_b2_dot = jacobianVectorProduct(output.output, wrt_dot, wrt); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + + double strand_loss_dot = + output.stack_length * M_PI * pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0 * sigma_b2_dot; + + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double loss = num_strands * strand_loss; + double loss_dot = num_strands * strand_loss_dot; + + double volume = calcOutput(output.volume, output.inputs); + // double volume_dot = jacobianVectorProduct(output.volume, wrt_dot, wrt); + + return loss_dot / volume; // - loss / pow(volume, 2) * volume_dot; + } else { return 0.0; @@ -1119,6 +1143,67 @@ void vectorJacobianProduct(ACLossFunctional &output, // M_PI * pow(output.radius, 4) * 2 * output.freq * // pow(2 * M_PI, 2) / 32.0; + /// double sigma_b2 = calcOutput(output.output, output.inputs); + mfem::Vector sigma_b2_bar_vec(&sigma_b2_bar, 1); + vectorJacobianProduct(output.output, sigma_b2_bar_vec, wrt, wrt_bar); + } + else if (wrt.rfind("peak_flux", 0) == 0) + { + double sigma_b2 = calcOutput(output.output, output.inputs); + + double strand_loss = sigma_b2 * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + double num_strands = + 2 * output.strands_in_hand * output.num_turns * output.num_slots; + + double loss = num_strands * strand_loss; + + double volume = calcOutput(output.volume, output.inputs); + + // double ac_loss = loss / volume; + + /// Start reverse pass... + double ac_loss_bar = out_bar(0); + + /// double ac_loss = loss / volume; + double loss_bar = ac_loss_bar / volume; + // double volume_bar = -ac_loss_bar * loss / pow(volume, 2); + + /// double volume = calcOutput(output.volume, inputs); + // mfem::Vector vol_bar_vec(&volume_bar, 1); + // vectorJacobianProduct(output.volume, vol_bar_vec, "state", wrt_bar); + + /// double loss = num_strands * strand_loss; + // double num_strands_bar = loss_bar * strand_loss; + double strand_loss_bar = loss_bar * num_strands; + + /// double num_strands = + /// 2 * output.strands_in_hand * output.num_turns * output.num_slots; + // double strands_in_hand_bar = + // num_strands_bar * 2 * output.num_turns * output.num_slots; + // double num_turns_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_slots; + // double num_slots_bar = + // num_strands_bar * 2 * output.strands_in_hand * output.num_turns; + + /// double strand_loss = sigma_b2 * output.stack_length * M_PI * + /// pow(output.radius, 4) * + /// pow(2 * M_PI * output.freq, 2) / 32.0; + double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * + pow(output.radius, 4) * + pow(2 * M_PI * output.freq, 2) / 32.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // pow(2 * M_PI * output.freq, 2) / 32.0; + // double strand_radius_bar = + // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length + // * + // M_PI * pow(output.radius, 4) * 2 * output.freq * + // pow(2 * M_PI, 2) / 32.0; + /// double sigma_b2 = calcOutput(output.output, output.inputs); mfem::Vector sigma_b2_bar_vec(&sigma_b2_bar, 1); vectorJacobianProduct(output.output, sigma_b2_bar_vec, wrt, wrt_bar); diff --git a/src/physics/electromagnetics/reluctivity_coefficient.cpp b/src/physics/electromagnetics/reluctivity_coefficient.cpp index 5ff79297..13a3d8af 100644 --- a/src/physics/electromagnetics/reluctivity_coefficient.cpp +++ b/src/physics/electromagnetics/reluctivity_coefficient.cpp @@ -46,6 +46,10 @@ class logNuBBSplineReluctivityCoefficient : public mach::StateCoefficient const mfem::IntegrationPoint &ip, double state) override; + double EvalState2ndDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) override; + void EvalRevDiff(const double Q_bar, mfem::ElementTransformation &trans, const mfem::IntegrationPoint &ip, @@ -61,6 +65,8 @@ class logNuBBSplineReluctivityCoefficient : public mach::StateCoefficient std::unique_ptr lognu; /// spline representing dlog(nu)/dB std::unique_ptr dlognudb; + /// spline representing d2log(nu)/dB2 + std::unique_ptr d2lognudb2; }; class BHBSplineReluctivityCoefficient : public mach::StateCoefficient @@ -414,6 +420,41 @@ double logNuBBSplineReluctivityCoefficient::EvalStateDeriv( } } +double logNuBBSplineReluctivityCoefficient::EvalState2ndDeriv( + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) +{ + if (d2lognudb2 == nullptr) + { + d2lognudb2 = std::make_unique(dlognudb->derive()); + } + if (state > b_max) + { + std::cout << "lognu state: " << state; + std::cout << " !!!LARGE STATE!!!"; + std::cout << "\n"; + } + + if (state <= b_max) + { + double lognu_val = lognu->eval(state).result()[0]; + double nu = exp(lognu_val); + + double dlognudb_val = dlognudb->eval(state).result()[0]; + // double dnudb = nu * dlognudb_val; + + double d2lognudb2_val = d2lognudb2->eval(state).result()[0]; + double d2nudb2 = nu * (d2lognudb2_val + pow(dlognudb_val, 2)); + + return d2nudb2; + } + else + { + return 0.0; + } +} + // double logNuBBSplineReluctivityCoefficient::EvalState2ndDeriv( // mfem::ElementTransformation &trans, // const mfem::IntegrationPoint &ip, diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index 376d42dc..e9932e74 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -26,6 +26,7 @@ set(REGRESSION_TEST_SRCS test_ac_loss test_magnetostatic_box_new # test_2d_magnet_in_box + test_magnetostatic_residual ) create_tests("${REGRESSION_TEST_SRCS}" regression_data.cpp) diff --git a/test/regression/test_magnetostatic_residual.cpp b/test/regression/test_magnetostatic_residual.cpp new file mode 100644 index 00000000..52855bef --- /dev/null +++ b/test/regression/test_magnetostatic_residual.cpp @@ -0,0 +1,617 @@ +#include + +#include "catch.hpp" +#include "mfem.hpp" +#include "nlohmann/json.hpp" + +#include "current_source_functions.hpp" +#include "mfem_common_integ.hpp" + +#include "magnetostatic_residual.hpp" + +/// Generate mesh +/// \param[in] nxy - number of nodes in the x and y directions +mfem::Mesh buildMesh(int nxy = 2); + +/// Simple nonlinear coefficient +class NonLinearCoefficient : public mach::StateCoefficient +{ +public: + double Eval(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) override + { + return 0.5*pow(state+1, -0.5); + } + + double EvalStateDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) override + { + return -0.25*pow(state+1, -1.5); + } + + double EvalState2ndDeriv(mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + const double state) override + { + return 0.375*pow(state+1, -2.5); + } + + void EvalRevDiff(const double Q_bar, + mfem::ElementTransformation &trans, + const mfem::IntegrationPoint &ip, + mfem::DenseMatrix &PointMat_Bar) override + {} +}; + +// TEST_CASE("CurrentLoad sensitivity wrt mesh_coords") +// { +// std::default_random_engine gen; +// std::uniform_real_distribution uniform_rand(-1.0,1.0); + +// using namespace mfem; + +// double delta = 1e-5; + +// // generate a 6 element mesh +// int num_edge = 2; +// auto smesh = buildMesh(num_edge); + +// mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + +// mesh.EnsureNodes(); +// const auto dim = mesh.SpaceDimension(); + +// for (int p = 1; p <= 1; ++p) +// { +// DYNAMIC_SECTION( "...for degree p = " << p ) +// { +// mfem::H1_FECollection fec(p, dim); +// mfem::ParFiniteElementSpace fes(&mesh, &fec); + +// std::map fields; +// fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); +// auto &state = fields.at("state"); + +// auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); +// auto *mesh_fespace = mesh_gf.ParFESpace(); +// /// create new state vector copying the mesh's fe space +// fields.emplace("mesh_coords", +// mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); +// auto &mesh_coords = fields.at("mesh_coords"); +// /// set the values of the new GF to those of the mesh's old nodes +// mesh_coords.gridFunc() = mesh_gf; +// /// tell the mesh to use this GF for its Nodes +// /// (and that it doesn't own it) +// mesh.NewNodes(mesh_coords.gridFunc(), false); + + +// mfem::Vector state_tv(state.space().GetTrueVSize()); +// for (int i = 0; i < state_tv.Size(); ++i) +// { +// state_tv(i) = uniform_rand(gen); +// } + +// mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); +// mesh_coords.setTrueVec(mesh_coords_tv); + +// mfem::Vector res_bar(state.space().GetTrueVSize()); +// for (int i = 0; i < res_bar.Size(); ++i) +// { +// res_bar(i) = uniform_rand(gen); +// } + +// mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); +// for (int i = 0; i < pert_vec.Size(); ++i) +// { +// pert_vec(i) = uniform_rand(gen); +// } + +// // res_bar = 0.0; +// // res_bar(0) = 1.0; +// // pert_vec = 0.0; +// // pert_vec(0) = 1.0; + +// auto options = R"({ +// "lin-prec": { +// "type": "hypreboomeramg", +// "printlevel": 0 +// }, +// "components": { +// "box1": { +// "attrs": [1], +// "material": { +// "name": "box1", +// "mu_r": 795774.7154594767 +// } +// }, +// "box2": { +// "attrs": [2], +// "material": { +// "name": "box2", +// "mu_r": 795774.7154594767 +// } +// } +// }, +// "current": { +// "box1": { +// "box1": [1] +// }, +// "box2": { +// "box2": [2] +// } +// }, +// "bcs": { +// "essential": "all" +// } +// })"_json; + +// auto materials = R"({ +// "box1": { +// "mu_r": 795774.715 +// }, +// "box2": { +// "mu_r": 795774.715 +// } +// })"_json; + +// auto &stack = mach::getDiffStack(); + +// mach::MachLinearForm load(fes, fields); + +// mach::CurrentDensityCoefficient2D current_coeff(stack, options["current"]); +// // mfem::ConstantCoefficient current_coeff(1.0); +// // mfem::FunctionCoefficient current_coeff( +// // [](const mfem::Vector &x) +// // { +// // return exp(-pow(x(0),2)); +// // }, +// // [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) +// // { +// // x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); +// // }); + +// load.addDomainIntegrator(new mach::DomainLFIntegrator(current_coeff)); + +// // mach::MagnetostaticResidual res(stack, fes, fields, options, materials, nu); + +// double current_density = 1.0; +// mach::MachInputs inputs{ +// {"state", state_tv}, +// {"mesh_coords", mesh_coords_tv}, +// {"current_density:box1", current_density}, +// {"current_density:box2", -current_density} +// }; + +// setInputs(load, inputs); +// setInputs(current_coeff, inputs); + +// mfem::Vector res_dot(state.space().GetTrueVSize()); +// res_dot = 0.0; +// jacobianVectorProduct(load, pert_vec, "mesh_coords", res_dot); +// double drdp_fwd = res_bar * res_dot; + +// mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); +// wrt_bar = 0.0; +// vectorJacobianProduct(load, res_bar, "mesh_coords", wrt_bar); +// double drdp_rev = wrt_bar * pert_vec; + +// // now compute the finite-difference approximation... +// mesh_coords_tv.Add(delta, pert_vec); +// mfem::Vector drdp_fd_p(state.space().GetTrueVSize()); +// drdp_fd_p = 0.0; +// setInputs(load, inputs); +// setInputs(current_coeff, inputs); +// addLoad(load, drdp_fd_p); + +// mesh_coords_tv.Add(-2 * delta, pert_vec); +// mfem::Vector drdp_fd_m(state.space().GetTrueVSize()); +// drdp_fd_m = 0.0; +// setInputs(load, inputs); +// setInputs(current_coeff, inputs); +// addLoad(load, drdp_fd_m); + +// mfem::Vector scratch(state.space().GetTrueVSize()); +// scratch = 0.0; +// scratch += drdp_fd_p; +// scratch -= drdp_fd_m; +// scratch /= (2 * delta); + +// double drdp_fd = res_bar * scratch; + +// std::cout << "drdp_rev: " << drdp_rev << " drdp_fd: " << drdp_fd << "\n"; +// // REQUIRE(drdp_fwd == Approx(drdp_fd).margin(1e-8)); +// REQUIRE(drdp_rev == Approx(drdp_fd).margin(1e-8)); +// mesh_coords_tv.Add(delta, pert_vec); +// } +// } +// } + +TEST_CASE("MagnetostaticResidual sensitivity wrt current_density") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + using namespace mfem; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto smesh = buildMesh(num_edge); + + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + auto &state = fields.at("state"); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + + mfem::Vector state_tv(state.space().GetTrueVSize()); + for (int i = 0; i < state_tv.Size(); ++i) + { + state_tv(i) = uniform_rand(gen); + } + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mfem::Vector res_bar(state.space().GetTrueVSize()); + for (int i = 0; i < res_bar.Size(); ++i) + { + res_bar(i) = uniform_rand(gen); + } + + auto options = R"({ + "lin-prec": { + "type": "hypreboomeramg", + "printlevel": 0 + }, + "components": { + "box1": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + } + }, + "box2": { + "attrs": [2], + "material": { + "name": "box2", + "mu_r": 795774.7154594767 + } + } + }, + "current": { + "box1": { + "box1": [1] + }, + "box2": { + "box2": [2] + } + }, + "bcs": { + "essential": "all" + } + })"_json; + + auto materials = R"({ + "box1": { + "mu_r": 795774.715 + }, + "box2": { + "mu_r": 795774.715 + } + })"_json; + + auto &stack = mach::getDiffStack(); + mach::MagnetostaticResidual res(stack, fes, fields, options, materials, nu); + + double current_density = 1.0; + mach::MachInputs inputs{ + {"state", state_tv}, + {"mesh_coords", mesh_coords_tv}, + {"current_density:box1", current_density}, + {"current_density:box2", -current_density} + }; + + double pert = uniform_rand(gen); + mfem::Vector pert_vec(&pert, 1); + + setInputs(res, inputs); + + mfem::Vector res_dot(state.space().GetTrueVSize()); + res_dot = 0.0; + jacobianVectorProduct(res, pert_vec, "current_density:box1", res_dot); + double drdp_fwd = res_bar * res_dot; + + double drdp_rev = vectorJacobianProduct(res, res_bar, "current_density:box1") * pert; + + // now compute the finite-difference approximation... + inputs["current_density:box1"] = current_density + pert * delta; + mfem::Vector drdp_fd_p(state.space().GetTrueVSize()); + drdp_fd_p = 0.0; + evaluate(res, inputs, drdp_fd_p); + + inputs["current_density:box1"] = current_density - pert * delta; + mfem::Vector drdp_fd_m(state.space().GetTrueVSize()); + drdp_fd_m = 0.0; + evaluate(res, inputs, drdp_fd_m); + + mfem::Vector scratch(state.space().GetTrueVSize()); + scratch = 0.0; + scratch += drdp_fd_p; + scratch -= drdp_fd_m; + scratch /= (2 * delta); + + double drdp_fd = res_bar * scratch; + + inputs["current_density:box1"] = current_density; + REQUIRE(drdp_fwd == Approx(drdp_fd).margin(1e-8)); + REQUIRE(drdp_rev == Approx(drdp_fd).margin(1e-8)); + + res_dot = 0.0; + jacobianVectorProduct(res, pert_vec, "current_density:box2", res_dot); + drdp_fwd = res_bar * res_dot; + + drdp_rev = vectorJacobianProduct(res, res_bar, "current_density:box2") * pert; + + // now compute the finite-difference approximation... + inputs["current_density:box2"] = -current_density + pert * delta; + drdp_fd_p = 0.0; + evaluate(res, inputs, drdp_fd_p); + + inputs["current_density:box2"] = -current_density - pert * delta; + drdp_fd_m = 0.0; + evaluate(res, inputs, drdp_fd_m); + + scratch = 0.0; + scratch += drdp_fd_p; + scratch -= drdp_fd_m; + scratch /= (2 * delta); + + drdp_fd = res_bar * scratch; + + REQUIRE(drdp_fwd == Approx(drdp_fd).margin(1e-8)); + REQUIRE(drdp_rev == Approx(drdp_fd).margin(1e-8)); + } + } +} + +TEST_CASE("MagnetostaticResidual sensitivity wrt mesh_coords") +{ + std::default_random_engine gen; + std::uniform_real_distribution uniform_rand(-1.0,1.0); + + using namespace mfem; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto smesh = buildMesh(num_edge); + + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + NonLinearCoefficient nu; + + for (int p = 1; p <= 1; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + auto &state = fields.at("state"); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + + mfem::Vector state_tv(state.space().GetTrueVSize()); + for (int i = 0; i < state_tv.Size(); ++i) + { + state_tv(i) = uniform_rand(gen); + } + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mfem::Vector res_bar(state.space().GetTrueVSize()); + for (int i = 0; i < res_bar.Size(); ++i) + { + res_bar(i) = uniform_rand(gen); + } + + mfem::Vector pert_vec(mesh_coords.space().GetTrueVSize()); + for (int i = 0; i < pert_vec.Size(); ++i) + { + pert_vec(i) = uniform_rand(gen); + } + + // res_bar = 0.0; + // res_bar(0) = 1.0; + // pert_vec = 0.0; + // pert_vec(0) = 1.0; + + auto options = R"({ + "lin-prec": { + "type": "hypreboomeramg", + "printlevel": 0 + }, + "components": { + "box1": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + } + }, + "box2": { + "attrs": [2], + "material": { + "name": "box2", + "mu_r": 795774.7154594767 + } + } + }, + "current": { + "box1": { + "box1": [1] + } + }, + "bcs": { + "essential": "all" + }, + "magnets": { + "Nd2Fe14B": { + "north": [2] + } + } + })"_json; + + auto materials = R"({ + "box1": { + "mu_r": 795774.715 + }, + "box2": { + "mu_r": 795774.715 + }, + "Nd2Fe14B": { + "B_r": 1.2 + } + })"_json; + + auto &stack = mach::getDiffStack(); + mach::MagnetostaticResidual res(stack, fes, fields, options, materials, nu); + + double current_density = 1.0; + mach::MachInputs inputs{ + {"state", state_tv}, + {"mesh_coords", mesh_coords_tv}, + {"current_density:box1", current_density}, + {"current_density:box2", -current_density} + }; + + setInputs(res, inputs); + + mfem::Vector res_dot(state.space().GetTrueVSize()); + res_dot = 0.0; + jacobianVectorProduct(res, pert_vec, "mesh_coords", res_dot); + double drdp_fwd = res_bar * res_dot; + + mfem::Vector wrt_bar(mesh_coords.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(res, res_bar, "mesh_coords", wrt_bar); + double drdp_rev = wrt_bar * pert_vec; + + // now compute the finite-difference approximation... + mesh_coords_tv.Add(delta, pert_vec); + mfem::Vector drdp_fd_p(state.space().GetTrueVSize()); + drdp_fd_p = 0.0; + setInputs(res, inputs); + evaluate(res, inputs, drdp_fd_p); + + mesh_coords_tv.Add(-2 * delta, pert_vec); + mfem::Vector drdp_fd_m(state.space().GetTrueVSize()); + drdp_fd_m = 0.0; + setInputs(res, inputs); + evaluate(res, inputs, drdp_fd_m); + + mfem::Vector scratch(state.space().GetTrueVSize()); + scratch = 0.0; + scratch += drdp_fd_p; + scratch -= drdp_fd_m; + scratch /= (2 * delta); + + double drdp_fd = res_bar * scratch; + + std::cout << "drdp_rev: " << drdp_rev << " drdp_fd: " << drdp_fd << "\n"; + // REQUIRE(drdp_fwd == Approx(drdp_fd).margin(1e-8)); + REQUIRE(drdp_rev == Approx(drdp_fd).margin(1e-8)); + mesh_coords_tv.Add(delta, pert_vec); + } + } +} + +mfem::Mesh buildMesh(int nxy) +{ + // generate a simple tet mesh + auto mesh = mfem::Mesh::MakeCartesian2D(nxy, nxy, + mfem::Element::TRIANGLE); + + // assign attributes to top and bottom sides + for (int i = 0; i < mesh.GetNE(); ++i) + { + auto *elem = mesh.GetElement(i); + + mfem::Array verts; + elem->GetVertices(verts); + + bool below = true; + for (int i = 0; i < verts.Size(); ++i) + { + auto *vtx = mesh.GetVertex(verts[i]); + // std::cout << "mesh vtx: " << vtx[0] << ", " << vtx[1] << "\n"; + if (vtx[1] <= 0.5) + { + below = below; + } + else + { + below = false; + } + } + if (below) + { + elem->SetAttribute(1); + } + else + { + elem->SetAttribute(2); + } + } + mesh.SetAttributes(); + + return mesh; +} \ No newline at end of file diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 9d10be61..f54ef619 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -75,6 +75,7 @@ set(EM_MPI_TEST_SRCS test_linesearch test_mesh_warper test_mach_nonlinearform + test_reluctivity_coeff ) create_tests("${FLUID_TEST_SRCS}" euler_test_data.cpp) diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index c0918cff..88ca0347 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -2267,6 +2267,72 @@ TEST_CASE("ACLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") } } +TEST_CASE("ACLossFunctionalIntegratorPeakFluxSens::AssembleRHSElementVect") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 8 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); + // auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + // Element::TETRAHEDRON); + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::ConstantCoefficient sigma(1.0); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION("...for degree p = " << p) + { + L2_FECollection fec(p, dim); + FiniteElementSpace fes(&mesh, &fec); + + // initialize state + GridFunction a(&fes); + FunctionCoefficient pert(randState); + a.ProjectCoefficient(pert); + + auto *integ = new mach::ACLossFunctionalIntegrator(sigma); + NonlinearForm functional(&fes); + functional.AddDomainIntegrator(integ); + + // extract mesh nodes and get their finite-element space + auto &x_nodes = *mesh.GetNodes(); + auto &mesh_fes = *x_nodes.FESpace(); + + // create v displacement field + GridFunction v(&fes); + v.ProjectCoefficient(pert); + + // initialize the vector that dJdx multiplies + GridFunction p(&fes); + p.ProjectCoefficient(pert); + + // evaluate dJdx and compute its product with p + LinearForm dJdu(&fes); + dJdu.AddDomainIntegrator( + new mach::ACLossFunctionalIntegratorPeakFluxSens(a, *integ)); + dJdu.Assemble(); + double dJdu_dot_p = dJdu * p; + + // now compute the finite-difference approximation... + GridFunction q_pert(a); + q_pert.Add(-delta, p); + double dJdu_dot_p_fd = -functional.GetEnergy(q_pert); + q_pert.Add(2 * delta, p); + dJdu_dot_p_fd += functional.GetEnergy(q_pert); + dJdu_dot_p_fd /= (2 * delta); + + REQUIRE(dJdu_dot_p == Approx(dJdu_dot_p_fd)); + } + } +} + TEST_CASE("ForceIntegrator3::GetElementEnergy") { using namespace mfem; diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp index 84b56528..11fadc46 100644 --- a/test/unit/test_electromag_outputs.cpp +++ b/test/unit/test_electromag_outputs.cpp @@ -312,6 +312,7 @@ TEST_CASE("ACLossFunctional sensitivity wrt frequency") double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); } @@ -802,6 +803,117 @@ TEST_CASE("ACLossFunctional sensitivity wrt mesh_coords") } } +TEST_CASE("ACLossFunctional sensitivity wrt peak_flux") +{ + using namespace mfem; + using namespace electromag_data; + + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 1; + auto smesh = Mesh::MakeCartesian2D(num_edge, + num_edge, + Element::TRIANGLE); + mfem::ParMesh mesh(MPI_COMM_WORLD, smesh); + + mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); + + mfem::FunctionCoefficient model( + [](const mfem::Vector &x) + { + return 1.0; + // return exp(-pow(x(0),2)); + }, + [](const mfem::Vector &x, const double q_bar, mfem::Vector &x_bar) + { + // x_bar(0) -= q_bar * 2 * x(0) * exp(-pow(x(0),2)); + }); + + for (int p = 1; p <= 4; ++p) + { + DYNAMIC_SECTION( "...for degree p = " << p ) + { + + mfem::H1_FECollection fec(p, dim); + mfem::ParFiniteElementSpace fes(&mesh, &fec); + + std::map fields; + fields.emplace("state", mach::FiniteElementState(mesh, fes, "state")); + auto &state = fields.at("state"); + mfem::Vector state_tv(state.space().GetTrueVSize()); + state_tv = 0.0; + + fields.emplace("peak_flux", + mach::FiniteElementState(mesh, + {{"degree", p}, + {"basis-type", "dg"}}, + 1, + "peak_flux")); + + auto &peak_flux = fields.at("peak_flux"); + mfem::Vector peak_flux_tv(peak_flux.space().GetTrueVSize()); + peak_flux.project(randState, peak_flux_tv); + + auto &mesh_gf = *dynamic_cast(mesh.GetNodes()); + auto *mesh_fespace = mesh_gf.ParFESpace(); + /// create new state vector copying the mesh's fe space + fields.emplace("mesh_coords", + mach::FiniteElementState(mesh, *mesh_fespace, "mesh_coords")); + auto &mesh_coords = fields.at("mesh_coords"); + /// set the values of the new GF to those of the mesh's old nodes + mesh_coords.gridFunc() = mesh_gf; + /// tell the mesh to use this GF for its Nodes + /// (and that it doesn't own it) + mesh.NewNodes(mesh_coords.gridFunc(), false); + + mfem::Vector mesh_coords_tv(mesh_coords.space().GetTrueVSize()); + mesh_coords.setTrueVec(mesh_coords_tv); + + mach::ACLossFunctional fun(fields, model, {}); + mach::MachInputs inputs{ + {"state", state_tv}, + {"peak_flux", peak_flux_tv}, + {"mesh_coords", mesh_coords_tv} + }; + + // initialize the vector that we use to perturb peak_flux + FunctionCoefficient pert(randState); + mfem::Vector pert_vec(peak_flux.space().GetTrueVSize()); + peak_flux.project(pert, pert_vec); + peak_flux.distributeSharedDofs(peak_flux_tv); + + double adjoint = randNumber(); + mfem::Vector adjoint_vec(&adjoint, 1); + + setInputs(fun, inputs); + double dfdp_fwd = adjoint * jacobianVectorProduct(fun, pert_vec, "peak_flux"); + + mfem::Vector wrt_bar(peak_flux.space().GetTrueVSize()); + wrt_bar = 0.0; + vectorJacobianProduct(fun, adjoint_vec, "peak_flux", wrt_bar); + double dfdp_rev = wrt_bar * pert_vec; + + + // now compute the finite-difference approximation... + peak_flux_tv.Add(delta, pert_vec); + double dfdp_fd_p = calcOutput(fun, inputs); + + peak_flux_tv.Add(-2 * delta, pert_vec); + double dfdp_fd_m = calcOutput(fun, inputs); + + double dfdp_fd = adjoint * (dfdp_fd_p - dfdp_fd_m) / (2 * delta); + + peak_flux_tv.Add(delta, pert_vec); // remember to reset the mesh nodes + std::cout << "dfdp_fwd: " << dfdp_fwd << " dfdp_fd: " << dfdp_fd << " dfdp_rev: " << dfdp_rev << "\n"; + + REQUIRE(dfdp_fwd == Approx(dfdp_fd).margin(1e-8)); + REQUIRE(dfdp_rev == Approx(dfdp_fd).margin(1e-8)); + } + } +} + TEST_CASE("CoreLossFunctional sensitivity wrt frequency") { using namespace mfem; diff --git a/test/unit/test_reluctivity_coeff.cpp b/test/unit/test_reluctivity_coeff.cpp new file mode 100644 index 00000000..0bee7260 --- /dev/null +++ b/test/unit/test_reluctivity_coeff.cpp @@ -0,0 +1,163 @@ +#include +#include + +#include "catch.hpp" +#include "mfem.hpp" +#include "nlohmann/json.hpp" + +#include "electromag_test_data.hpp" +#include "reluctivity_coefficient.hpp" + +namespace +{ +// String used to define a single element mesh, with a c-shaped quad element +std::string mesh_str = + "MFEM mesh v1.0" "\n\n" + "dimension" "\n" + "2" "\n\n" + "elements" "\n" + "1" "\n" + "1 3 0 1 2 3" "\n\n" + "boundary" "\n" + "0" "\n\n" + "vertices" "\n" + "4" "\n\n" + "nodes" "\n" + "FiniteElementSpace" "\n" + "FiniteElementCollection: Quadratic" "\n" + "VDim: 2" "\n" + "Ordering: 1" "\n" + "0 0" "\n" + "0 2" "\n" + "0 6" "\n" + "0 8" "\n" + "0 1" "\n" + "-6 4" "\n" + "0 7" "\n" + "-8 4" "\n" + "-7 4" "\n"; + +} // anonymous namespace + +TEST_CASE("logNuBBSplineReluctivityCoefficient::EvalStateDeriv") +{ + constexpr double eps_fd = 1e-5; + std::default_random_engine generator; + std::uniform_real_distribution distribution(0.1,2.0); + + // Create quadratic mesh with single C-shaped quadrilateral + std::stringstream meshStr; + meshStr << mesh_str; + mfem::Mesh mesh(meshStr); + + auto component = R"({ + "components": { + "test": { + "attrs": [1], + "material": { + "name": "hiperco50", + "reluctivity": { + "model": "lognu", + "cps": [5.5286, 5.4645, 4.5597, 4.2891, 3.8445, 4.2880, 4.9505, 11.9364, 11.9738, 12.6554, 12.8097, 13.3347, 13.5871, 13.5871, 13.5871], + "knots": [0, 0, 0, 0, 0.1479, 0.5757, 0.9924, 1.4090, 1.8257, 2.2424, 2.6590, 3.0757, 3.4924, 3.9114, 8.0039, 10.0000, 10.0000, 10.0000, 10.0000], + "degree": 3 + } + } + } + } + })"_json; + mach::ReluctivityCoefficient coeff(component, {}); + + for (int p = 1; p <= 4; ++p) + { + const int dim = mesh.Dimension(); + mfem::H1_FECollection fec(p, dim); + mfem::FiniteElementSpace fes(&mesh, &fec); + + const auto &el = *fes.GetFE(0); + mfem::IsoparametricTransformation trans; + mesh.GetElementTransformation(0, &trans); + + int order = 2 * el.GetOrder() - 2; + const auto *ir = &mfem::IntRules.Get(el.GetGeomType(), order); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double state = distribution(generator); + double deriv = coeff.EvalStateDeriv(trans, ip, state); + + state += eps_fd; + double deriv_fd_p = coeff.Eval(trans, ip, state); + state -= 2.0*eps_fd; + double deriv_fd_m = coeff.Eval(trans, ip, state); + + double deriv_fd = (deriv_fd_p - deriv_fd_m) / (2.0 * eps_fd); + REQUIRE(deriv == Approx(deriv_fd)); + } + } + +} + +TEST_CASE("logNuBBSplineReluctivityCoefficient::EvalState2ndDeriv") +{ + constexpr double eps_fd = 1e-5; + std::default_random_engine generator; + std::uniform_real_distribution distribution(0.1,2.0); + + // Create quadratic mesh with single C-shaped quadrilateral + std::stringstream meshStr; + meshStr << mesh_str; + mfem::Mesh mesh(meshStr); + + auto component = R"({ + "components": { + "test": { + "attrs": [1], + "material": { + "name": "hiperco50", + "reluctivity": { + "model": "lognu", + "cps": [5.5286, 5.4645, 4.5597, 4.2891, 3.8445, 4.2880, 4.9505, 11.9364, 11.9738, 12.6554, 12.8097, 13.3347, 13.5871, 13.5871, 13.5871], + "knots": [0, 0, 0, 0, 0.1479, 0.5757, 0.9924, 1.4090, 1.8257, 2.2424, 2.6590, 3.0757, 3.4924, 3.9114, 8.0039, 10.0000, 10.0000, 10.0000, 10.0000], + "degree": 3 + } + } + } + } + })"_json; + mach::ReluctivityCoefficient coeff(component, {}); + + for (int p = 1; p <= 4; ++p) + { + const int dim = mesh.Dimension(); + mfem::H1_FECollection fec(p, dim); + mfem::FiniteElementSpace fes(&mesh, &fec); + + const auto &el = *fes.GetFE(0); + mfem::IsoparametricTransformation trans; + mesh.GetElementTransformation(0, &trans); + + int order = 2 * el.GetOrder() - 2; + const auto *ir = &mfem::IntRules.Get(el.GetGeomType(), order); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + double state = distribution(generator); + double second_deriv = coeff.EvalState2ndDeriv(trans, ip, state); + + state += eps_fd; + double second_deriv_fd_p = coeff.EvalStateDeriv(trans, ip, state); + state -= 2.0*eps_fd; + double second_deriv_fd_m = coeff.EvalStateDeriv(trans, ip, state); + + double second_deriv_fd = (second_deriv_fd_p - second_deriv_fd_m) / (2.0 * eps_fd); + REQUIRE(second_deriv == Approx(second_deriv_fd)); + } + } +} \ No newline at end of file From 072073e5cf813b3f96699162aad4e440afcddc74 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:45:43 -0500 Subject: [PATCH 64/72] make format and added finalizeAdjointSystem function to the mesh warper residual --- src/common/functional_output.cpp | 4 ++-- src/physics/common_outputs.hpp | 7 +++---- src/physics/mfem_common_integ.hpp | 8 +++++--- src/utils/mesh_warper/mesh_warper.cpp | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/common/functional_output.cpp b/src/common/functional_output.cpp index da8d73e4..31d88c66 100644 --- a/src/common/functional_output.cpp +++ b/src/common/functional_output.cpp @@ -113,8 +113,8 @@ double jacobianVectorProduct(FunctionalOutput &output, auto wrt_key = wrt.substr(0, wrt.find(':')); // output.scratch(0) = // output.output_scalar_sens.at(wrt).GetEnergy(state); - // TODO: need to confirm what OpenMDAO expects in terms of parallel outputs: - // should we give it the already reduced value or not? + // TODO: need to confirm what OpenMDAO expects in terms of parallel + // outputs: should we give it the already reduced value or not? output.scratch(0) = output.output_scalar_sens.at(wrt_key).GetEnergy(state_gf); } diff --git a/src/physics/common_outputs.hpp b/src/physics/common_outputs.hpp index dc99c256..9885eb12 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -241,10 +241,9 @@ class IEAggregateFunctional return num / denom; } - friend double jacobianVectorProduct( - IEAggregateFunctional &output, - const mfem::Vector &wrt_dot, - const std::string &wrt); + friend double jacobianVectorProduct(IEAggregateFunctional &output, + const mfem::Vector &wrt_dot, + const std::string &wrt); friend void vectorJacobianProduct(IEAggregateFunctional &output, const mfem::Vector &out_bar, diff --git a/src/physics/mfem_common_integ.hpp b/src/physics/mfem_common_integ.hpp index a3991b7b..08c79881 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -320,15 +320,17 @@ class IEAggregateIntegratorDenominator : public mfem::NonlinearFormIntegrator friend class IEAggregateIntegratorDenominatorMeshSens; }; -class IEAggregateIntegratorDenominatorMeshSens : public mfem::LinearFormIntegrator +class IEAggregateIntegratorDenominatorMeshSens + : public mfem::LinearFormIntegrator { public: /// \brief - Compute forces/torques based on the virtual work method /// \param[in] state - the state vector to evaluate force at /// \param[in] integ - reference to primal integrator that holds inputs for /// integrators - IEAggregateIntegratorDenominatorMeshSens(mfem::GridFunction &state, - IEAggregateIntegratorDenominator &integ) + IEAggregateIntegratorDenominatorMeshSens( + mfem::GridFunction &state, + IEAggregateIntegratorDenominator &integ) : state(state), integ(integ) { } diff --git a/src/utils/mesh_warper/mesh_warper.cpp b/src/utils/mesh_warper/mesh_warper.cpp index 18a1298d..d597bb2b 100644 --- a/src/utils/mesh_warper/mesh_warper.cpp +++ b/src/utils/mesh_warper/mesh_warper.cpp @@ -43,6 +43,12 @@ class MeshWarperResidual final mfem::Vector &state_bar, mfem::Vector &adjoint); + friend void finalizeAdjointSystem(MeshWarperResidual &residual, + mfem::Solver &adj_solver, + const mach::MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint); + friend double jacobianVectorProduct(MeshWarperResidual &residual, const mfem::Vector &wrt_dot, const std::string &wrt); @@ -168,6 +174,15 @@ void setUpAdjointSystem(MeshWarperResidual &residual, setUpAdjointSystem(residual.res, adj_solver, inputs, state_bar, adjoint); } +void finalizeAdjointSystem(MeshWarperResidual &residual, + mfem::Solver &adj_solver, + const mach::MachInputs &inputs, + mfem::Vector &state_bar, + mfem::Vector &adjoint) +{ + finalizeAdjointSystem(residual.res, adj_solver, inputs, state_bar, adjoint); +} + double jacobianVectorProduct(MeshWarperResidual &residual, const mfem::Vector &wrt_dot, const std::string &wrt) From e9338c748c81c998cc7c800469a4de4e25627fe4 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 14 Nov 2022 13:50:40 -0500 Subject: [PATCH 65/72] change github actions runner to now explicitly make homebrew available --- .github/workflows/tests.yml | 4 ++++ .github/workflows/tidy.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ac9989c..cf9d9c6d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,6 +45,10 @@ jobs: } steps: + - name: Set up Homebrew + id: set-up-homebrew + uses: Homebrew/actions/setup-homebrew@master + - name: Download Ninja id: cmake_and_ninja shell: cmake -P {0} diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index 8a3ab295..2aaa9fae 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -26,6 +26,10 @@ jobs: buildflags: "-fno-openmp", } steps: + - name: Set up Homebrew + id: set-up-homebrew + uses: Homebrew/actions/setup-homebrew@master + - name: Clone Mach uses: actions/checkout@v2 with: From 17290ebb04a3a9994c2e69bf0e858865bdbbe1d8 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 5 Dec 2022 14:15:36 -0500 Subject: [PATCH 66/72] minor changes calling terminal hook in derived PDE solvers, logging temperature field in magnetostatic solver if it exists --- src/common/abstract_solver.cpp | 2 ++ .../electromagnetics/magnetostatic.cpp | 34 +++++++++++++++++++ .../electromagnetics/magnetostatic.hpp | 14 ++++---- src/physics/mach_nonlinearform.cpp | 10 +++--- src/physics/pde_solver.cpp | 1 + 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/common/abstract_solver.cpp b/src/common/abstract_solver.cpp index 1ab46f49..a0bd3d81 100644 --- a/src/common/abstract_solver.cpp +++ b/src/common/abstract_solver.cpp @@ -126,6 +126,8 @@ void AbstractSolver2::solveForState(const MachInputs &inputs, mfem::Vector zero; nonlinear_solver->Mult(zero, state); + terminalHook(1, 1.0, state); + /// log final state for (auto &[logger, options] : loggers) { diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index da02bca3..f9de77fc 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include "data_logging.hpp" #include "mfem.hpp" #include "nlohmann/json.hpp" @@ -90,6 +92,16 @@ MagnetostaticSolver::MagnetostaticSolver(MPI_Comm comm, mach::ParaViewLogger paraview("magnetostatic", &mesh()); paraview.registerField("state", fields.at("state").gridFunc()); paraview.registerField("adjoint", fields.at("adjoint").gridFunc()); + paraview.registerField( + "residual", + dynamic_cast(duals.at("residual").localVec())); + + const auto &temp_field_iter = fields.find("temperature"); + if (temp_field_iter != fields.end()) + { + auto &temp_field = temp_field_iter->second; + paraview.registerField("temperature", temp_field.gridFunc()); + } addLogger(std::move(paraview), {}); } @@ -159,6 +171,19 @@ void MagnetostaticSolver::addOutput(const std::string &fun, std::forward_as_tuple(fun), std::forward_as_tuple(mesh(), dg_field_options)); + // mach::ParaViewLogger paraview("flux_magnitude", &mesh()); + // paraview.registerField("flux_magnitude", + // fields.at("flux_magnitude").gridFunc()); addLogger(std::move(paraview), + // {}); + + auto &[logger, logger_opts] = loggers.back(); + if (std::holds_alternative(logger)) + { + auto ¶view = std::get(logger); + paraview.registerField("flux_magnitude", + fields.at("flux_magnitude").gridFunc()); + } + auto &dg_field = fields.at(fun); L2CurlMagnitudeProjection out( state(), fields.at("mesh_coords"), dg_field); @@ -273,6 +298,15 @@ void MagnetostaticSolver::addOutput(const std::string &fun, } } +void MagnetostaticSolver::derivedPDETerminalHook(int iter, + double t_final, + const mfem::Vector &state) +{ + work.SetSize(state.Size()); + calcResidual(state, work); + res_vec().distributeSharedDofs(work); +} + } // namespace mach // using namespace std; diff --git a/src/physics/electromagnetics/magnetostatic.hpp b/src/physics/electromagnetics/magnetostatic.hpp index 03ca032a..2e6d8063 100644 --- a/src/physics/electromagnetics/magnetostatic.hpp +++ b/src/physics/electromagnetics/magnetostatic.hpp @@ -46,13 +46,13 @@ class MagnetostaticSolver : public PDESolver // double dt, // const mfem::Vector &state) override; - // /// Code that should be executed after time stepping ends - // /// \param[in] iter - the terminal iteration - // /// \param[in] t_final - the final time - // /// \param[in] state - the current state - // void derivedPDETerminalHook(int iter, - // double t_final, - // const mfem::Vector &state) override; + /// Code that should be executed after time stepping ends + /// \param[in] iter - the terminal iteration + /// \param[in] t_final - the final time + /// \param[in] state - the current state + void derivedPDETerminalHook(int iter, + double t_final, + const mfem::Vector &state) override; // /// Find the step size based on the options; e.g. for constant CFL or PTC // /// \param[in] iter - the current iteration diff --git a/src/physics/mach_nonlinearform.cpp b/src/physics/mach_nonlinearform.cpp index c3c73845..ba8d6727 100644 --- a/src/physics/mach_nonlinearform.cpp +++ b/src/physics/mach_nonlinearform.cpp @@ -180,11 +180,11 @@ void finalizeAdjointSystem(MachNonlinearForm &form, mfem::Vector &state_bar, mfem::Vector &adjoint) { - const auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); - if (ess_tdof_list.Size() == 0) - { - return; - } + // const auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); + // if (ess_tdof_list.Size() == 0) + // { + // return; + // } // adjoint.SetSubVector(ess_tdof_list, form.scratch); diff --git a/src/physics/pde_solver.cpp b/src/physics/pde_solver.cpp index 3b81eb92..223483b3 100644 --- a/src/physics/pde_solver.cpp +++ b/src/physics/pde_solver.cpp @@ -525,6 +525,7 @@ void PDESolver::terminalHook(int iter, const mfem::Vector &state) { AbstractSolver2::terminalHook(iter, t_final, state); + derivedPDETerminalHook(iter, t_final, state); } } // namespace mach From a9171b5b0b974d783975ef6ca2a091a3d7652126 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 5 Dec 2022 14:16:03 -0500 Subject: [PATCH 67/72] make format --- src/common/abstract_solver.hpp | 6 ++---- src/physics/fluidflow/euler.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/common/abstract_solver.hpp b/src/common/abstract_solver.hpp index ce7977c9..942e647b 100644 --- a/src/common/abstract_solver.hpp +++ b/src/common/abstract_solver.hpp @@ -426,8 +426,7 @@ void AbstractSolver2::setState(T function, { return std::make_any(function); } - } - (); + }(); setState_(any, name, state); } } @@ -466,8 +465,7 @@ double AbstractSolver2::calcStateError(T ex_sol, { return std::make_any(ex_sol); } - } - (); + }(); return calcStateError_(any, name, state); } } diff --git a/src/physics/fluidflow/euler.hpp b/src/physics/fluidflow/euler.hpp index c3e2286e..21f7a966 100644 --- a/src/physics/fluidflow/euler.hpp +++ b/src/physics/fluidflow/euler.hpp @@ -1,7 +1,7 @@ #ifndef MACH_EULER #define MACH_EULER -//#include +// #include #include "mfem.hpp" From 379dfb753ff37f89ae45b87e15bf9ab129b59585 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Sat, 28 Jan 2023 17:44:36 -0500 Subject: [PATCH 68/72] fix bug in AC loss calc that had 32 in denominator instead of 8 --- .../electromagnetics/electromag_outputs.cpp | 125 +++++++++--------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 1487e1f2..a0ae6411 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -530,8 +530,7 @@ double calcOutput(ACLossFunctional &output, const MachInputs &inputs) double sigma_b2 = calcOutput(output.output, output.inputs); double strand_loss = sigma_b2 * output.stack_length * M_PI * - pow(output.radius, 4) * pow(2 * M_PI * output.freq, 2) / - 32.0; + pow(output.radius, 4) * pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -553,10 +552,10 @@ double jacobianVectorProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double strand_loss_dot = 4 * sigma_b2 * output.stack_length * M_PI * pow(output.radius, 3) * - pow(2 * M_PI * output.freq, 2) / 32.0 * wrt_dot(0); + pow(2 * M_PI * output.freq, 2) / 8.0 * wrt_dot(0); double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -574,10 +573,10 @@ double jacobianVectorProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double strand_loss_dot = 2 * sigma_b2 * output.stack_length * M_PI * pow(output.radius, 3) * output.freq * - pow(2 * M_PI, 2) / 32.0 * wrt_dot(0); + pow(2 * M_PI, 2) / 8.0 * wrt_dot(0); double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -595,9 +594,9 @@ double jacobianVectorProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double strand_loss_dot = sigma_b2 * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0 * + pow(2 * M_PI * output.freq, 2) / 8.0 * wrt_dot(0); double num_strands = @@ -616,7 +615,7 @@ double jacobianVectorProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -637,7 +636,7 @@ double jacobianVectorProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -658,7 +657,7 @@ double jacobianVectorProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -680,11 +679,11 @@ double jacobianVectorProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; double strand_loss_dot = output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0 * sigma_b2_dot; + pow(2 * M_PI * output.freq, 2) / 8.0 * sigma_b2_dot; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -704,11 +703,11 @@ double jacobianVectorProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; double strand_loss_dot = output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0 * sigma_b2_dot; + pow(2 * M_PI * output.freq, 2) / 8.0 * sigma_b2_dot; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -737,7 +736,7 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -772,20 +771,20 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double strand_radius_bar = strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; return strand_radius_bar; } @@ -795,7 +794,7 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -830,19 +829,19 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * 2 * output.freq * - pow(2 * M_PI, 2) / 32.0; + pow(2 * M_PI, 2) / 8.0; return frequency_bar; } @@ -852,7 +851,7 @@ double vectorJacobianProduct(ACLossFunctional &output, // double strand_loss = sigma_b2 * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -887,20 +886,20 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; return stack_length_bar; } @@ -910,7 +909,7 @@ double vectorJacobianProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -945,20 +944,20 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; return strands_in_hand_bar; } else if (wrt.rfind("num_turns", 0) == 0) @@ -967,7 +966,7 @@ double vectorJacobianProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -1002,20 +1001,20 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; return num_turns_bar; } else if (wrt.rfind("num_slots", 0) == 0) @@ -1024,7 +1023,7 @@ double vectorJacobianProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double num_strands = // 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -1059,20 +1058,20 @@ double vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; // double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; return num_slots_bar; } else @@ -1092,7 +1091,7 @@ void vectorJacobianProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -1128,20 +1127,20 @@ void vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; /// double sigma_b2 = calcOutput(output.output, output.inputs); mfem::Vector sigma_b2_bar_vec(&sigma_b2_bar, 1); @@ -1153,7 +1152,7 @@ void vectorJacobianProduct(ACLossFunctional &output, double strand_loss = sigma_b2 * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; @@ -1189,20 +1188,20 @@ void vectorJacobianProduct(ACLossFunctional &output, /// double strand_loss = sigma_b2 * output.stack_length * M_PI * /// pow(output.radius, 4) * - /// pow(2 * M_PI * output.freq, 2) / 32.0; + /// pow(2 * M_PI * output.freq, 2) / 8.0; double sigma_b2_bar = strand_loss_bar * output.stack_length * M_PI * pow(output.radius, 4) * - pow(2 * M_PI * output.freq, 2) / 32.0; + pow(2 * M_PI * output.freq, 2) / 8.0; // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * // pow(output.radius, 4) * - // pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(2 * M_PI * output.freq, 2) / 8.0; // double strand_radius_bar = // strand_loss_bar * sigma_b2 * output.stack_length * M_PI * 4 * - // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 32.0; + // pow(output.radius, 3) * pow(2 * M_PI * output.freq, 2) / 8.0; // double frequency_bar = strand_loss_bar * sigma_b2 * output.stack_length // * // M_PI * pow(output.radius, 4) * 2 * output.freq * - // pow(2 * M_PI, 2) / 32.0; + // pow(2 * M_PI, 2) / 8.0; /// double sigma_b2 = calcOutput(output.output, output.inputs); mfem::Vector sigma_b2_bar_vec(&sigma_b2_bar, 1); From 49963cf4bfc9f87ce728b027ae3d142e9e1efd8d Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Sat, 28 Jan 2023 17:48:02 -0500 Subject: [PATCH 69/72] make format --- src/physics/electromagnetics/electromag_outputs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index a0ae6411..d90cfaeb 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -530,7 +530,8 @@ double calcOutput(ACLossFunctional &output, const MachInputs &inputs) double sigma_b2 = calcOutput(output.output, output.inputs); double strand_loss = sigma_b2 * output.stack_length * M_PI * - pow(output.radius, 4) * pow(2 * M_PI * output.freq, 2) / 8.0; + pow(output.radius, 4) * pow(2 * M_PI * output.freq, 2) / + 8.0; double num_strands = 2 * output.strands_in_hand * output.num_turns * output.num_slots; From 81952adf172ca3c23c3aeb2adf685d9ead6c3dfb Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 27 Mar 2023 14:36:19 -0600 Subject: [PATCH 70/72] update mach builder to allow promoting inputs with aliases --- mach/mphys/mach_builder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mach/mphys/mach_builder.py b/mach/mphys/mach_builder.py index 0d1b765e..361e199c 100644 --- a/mach/mphys/mach_builder.py +++ b/mach/mphys/mach_builder.py @@ -88,7 +88,10 @@ def setup(self): solver_options = self.solver.getOptions() mesh_input = "x_" + _getPhysicsAbbreviation(solver_options) + "_vol" state_input = _getPhysicsAbbreviation(solver_options) + "_state" - promoted_inputs = [("mesh_coords", mesh_input), ("state", state_input)] + promoted_inputs = { + "mesh_coords": ("mesh_coords", mesh_input), + "state": ("state", state_input) + } for output in self.outputs: if "options" in self.outputs[output]: @@ -101,12 +104,13 @@ def setup(self): else: depends = [] + depends = [promoted_inputs[input] if input in promoted_inputs else input for input in depends] self.add_subsystem(output, MachFunctional(solver=self.solver, func=output, func_options=output_opts, depends=depends), - promotes_inputs=[*depends, *promoted_inputs], + promotes_inputs=[*depends], promotes_outputs=[output]) From 1d3c45e7f903b758d29e68e7d3ea6db47a5c4fa1 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 27 Mar 2023 14:37:02 -0600 Subject: [PATCH 71/72] fix bug when computing core losses that would cause all core loss calculations to depend on one peak flux value --- src/physics/electromagnetics/electromag_outputs.cpp | 8 +++++--- src/physics/electromagnetics/electromag_outputs.hpp | 3 ++- src/physics/electromagnetics/magnetostatic.cpp | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index d90cfaeb..0d53d2e6 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1270,7 +1270,8 @@ CoreLossFunctional::CoreLossFunctional( std::map &fields, const nlohmann::json &components, const nlohmann::json &materials, - const nlohmann::json &options) + const nlohmann::json &options, + std::string attr_name) : output(fields.at("state").space(), fields), rho(constructMaterialCoefficient("rho", components, materials)), k_s(constructMaterialCoefficient("ks", components, materials)), @@ -1281,13 +1282,14 @@ CoreLossFunctional::CoreLossFunctional( { auto attributes = options["attributes"].get>(); output.addOutputDomainIntegrator( - new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta, "stator"), + new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta, std::move(attr_name)), attributes); + } else { output.addOutputDomainIntegrator( - new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta)); + new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta, std::move(attr_name))); } } diff --git a/src/physics/electromagnetics/electromag_outputs.hpp b/src/physics/electromagnetics/electromag_outputs.hpp index 548cdd19..f40a0389 100644 --- a/src/physics/electromagnetics/electromag_outputs.hpp +++ b/src/physics/electromagnetics/electromag_outputs.hpp @@ -357,7 +357,8 @@ class CoreLossFunctional final CoreLossFunctional(std::map &fields, const nlohmann::json &components, const nlohmann::json &materials, - const nlohmann::json &options); + const nlohmann::json &options, + std::string attr_name = ""); private: FunctionalOutput output; diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index f9de77fc..5709eb31 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -249,8 +249,9 @@ void MagnetostaticSolver::addOutput(const std::string &fun, std::forward_as_tuple("peak_flux"), std::forward_as_tuple(mesh(), dg_field_options)); + auto attr_name = fun.substr(fun.find(":")+1); CoreLossFunctional out( - fields, AbstractSolver2::options["components"], materials, options); + fields, AbstractSolver2::options["components"], materials, options, std::move(attr_name)); outputs.emplace(fun, std::move(out)); } else if (fun.rfind("mass", 0) == 0) From dd6e700a393a8eb25c0b31ebcae1f95780db8a16 Mon Sep 17 00:00:00 2001 From: Tucker Babcock Date: Mon, 27 Mar 2023 14:39:23 -0600 Subject: [PATCH 72/72] make format --- src/physics/electromagnetics/electromag_outputs.cpp | 8 ++++---- src/physics/electromagnetics/magnetostatic.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/physics/electromagnetics/electromag_outputs.cpp b/src/physics/electromagnetics/electromag_outputs.cpp index 0d53d2e6..55672fc8 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1282,14 +1282,14 @@ CoreLossFunctional::CoreLossFunctional( { auto attributes = options["attributes"].get>(); output.addOutputDomainIntegrator( - new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta, std::move(attr_name)), + new SteinmetzLossIntegrator( + *rho, *k_s, *alpha, *beta, std::move(attr_name)), attributes); - } else { - output.addOutputDomainIntegrator( - new SteinmetzLossIntegrator(*rho, *k_s, *alpha, *beta, std::move(attr_name))); + output.addOutputDomainIntegrator(new SteinmetzLossIntegrator( + *rho, *k_s, *alpha, *beta, std::move(attr_name))); } } diff --git a/src/physics/electromagnetics/magnetostatic.cpp b/src/physics/electromagnetics/magnetostatic.cpp index 5709eb31..1f19a6c7 100644 --- a/src/physics/electromagnetics/magnetostatic.cpp +++ b/src/physics/electromagnetics/magnetostatic.cpp @@ -249,9 +249,12 @@ void MagnetostaticSolver::addOutput(const std::string &fun, std::forward_as_tuple("peak_flux"), std::forward_as_tuple(mesh(), dg_field_options)); - auto attr_name = fun.substr(fun.find(":")+1); - CoreLossFunctional out( - fields, AbstractSolver2::options["components"], materials, options, std::move(attr_name)); + auto attr_name = fun.substr(fun.find(":") + 1); + CoreLossFunctional out(fields, + AbstractSolver2::options["components"], + materials, + options, + std::move(attr_name)); outputs.emplace(fun, std::move(out)); } else if (fun.rfind("mass", 0) == 0)