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/.github/workflows/format.yml b/.github/workflows/format.yml index 2cdfcc8b..6a939fd9 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@v4.8.0 with: - clang-format-version: '12' check-path: ${{ matrix.path }} 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index 8876c180..5decfcc1 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) @@ -169,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" + # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++17;" ) endif (MACH_USE_CLANG_TIDY) @@ -281,10 +283,13 @@ 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 + #GIT_TAG v2.7.1 + GIT_TAG v2.10.0 ) FetchContent_MakeAvailable(pybind11) @@ -305,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" # CXX_CLANG_TIDY "clang-tidy;--fix;--fix-errors;--format-style=file;--extra-arg=--std=c++11" ) endif (MACH_USE_CLANG_TIDY) diff --git a/mach/mach_output.py b/mach/mach_output.py index 1c04d6cb..51b7ec10 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) @@ -112,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: @@ -123,12 +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"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]} for fun {func}") solver.outputJacobianVectorProduct(of=func, inputs=input_dict, wrt_dot=d_inputs[input], @@ -145,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: @@ -163,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 b16f0375..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) @@ -58,24 +76,24 @@ def setup(self): ext_fields = "external-fields" in solver_options for input in self.options["depends"]: if input == "mesh_coords": - self.add_input("mesh_coords", - distributed=True, - shape_by_conn=True, - desc="volume mesh node coordinates", - tags=["mphys_coordinates"]) + 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.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) @@ -90,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) @@ -115,9 +135,9 @@ 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"] + residual = self.vectors["state_res"] solver.calcResidual(input_dict, residual) residuals["state"][:] = residual[:] @@ -138,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) @@ -160,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"] @@ -171,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"]: @@ -207,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/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]) diff --git a/mach/pyMach/machSolver.cpp b/mach/pyMach/machSolver.cpp index e7d0b911..ea4b39f3 100644 --- a/mach/pyMach/machSolver.cpp +++ b/mach/pyMach/machSolver.cpp @@ -43,28 +43,38 @@ 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, - 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, - 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); + // self.setState(fun, state_vec, name); + self.setState(cpp_fun, state_vec, name); }, py::arg("fun"), py::arg("state"), @@ -90,7 +100,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 +112,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 +122,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 +253,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/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..83206174 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"]) @@ -281,10 +330,48 @@ def test_flux_density(self): 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) + # 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/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/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/abstract_solver.cpp b/src/common/abstract_solver.cpp index 256f5ebc..a0bd3d81 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" @@ -125,10 +126,11 @@ void AbstractSolver2::solveForState(const MachInputs &inputs, mfem::Vector zero; nonlinear_solver->Mult(zero, state); + terminalHook(1, 1.0, 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); } } @@ -165,10 +167,11 @@ void AbstractSolver2::solveForAdjoint(const MachInputs &inputs, adj_solver->Mult(work, adjoint); + finalizeAdjointSystem(*spatial_res, *adj_solver, inputs, 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); } } @@ -380,28 +383,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, @@ -410,28 +413,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) @@ -516,10 +519,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 +533,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 +569,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..942e647b 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 @@ -427,8 +426,7 @@ void AbstractSolver2::setState(T function, { return std::make_any(function); } - } - (); + }(); setState_(any, name, state); } } @@ -467,8 +465,7 @@ double AbstractSolver2::calcStateError(T ex_sol, { return std::make_any(ex_sol); } - } - (); + }(); return calcStateError_(any, name, state); } } diff --git a/src/common/coefficient.cpp b/src/common/coefficient.cpp index feeeca4b..a3af9460 100644 --- a/src/common/coefficient.cpp +++ b/src/common/coefficient.cpp @@ -202,12 +202,22 @@ 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); - 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) { @@ -216,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)); @@ -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/functional_output.cpp b/src/common/functional_output.cpp index 44fd957c..31d88c66 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()) @@ -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(), @@ -104,13 +104,47 @@ 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); + 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 + { + 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); + 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); +} + 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 db122e63..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, @@ -68,7 +63,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 +90,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) @@ -141,13 +137,14 @@ 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 -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()); @@ -156,8 +153,8 @@ void FunctionalOutput::addOutputDomainIntegrator(T *integrator, 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 @@ -165,7 +162,7 @@ void FunctionalOutput::addOutputInteriorFaceIntegrator(T *integrator) { integs.emplace_back(*integrator); output.AddInteriorFaceIntegrator(integrator); - addSensitivityIntegrator( + addInteriorFaceSensitivityIntegrator( *integrator, *func_fields, output_sens, output_scalar_sens); } @@ -174,14 +171,14 @@ 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 void FunctionalOutput::addOutputBdrFaceIntegrator( T *integrator, - std::vector bdr_attr_marker) + const std::vector &bdr_attr_marker) { integs.emplace_back(*integrator); @@ -190,8 +187,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/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/linesearch.cpp b/src/common/linesearch.cpp new file mode 100644 index 00000000..212a5cb0 --- /dev/null +++ b/src/common/linesearch.cpp @@ -0,0 +1,138 @@ +#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) +{ + 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 " << iter << ": alpha = " << alpha2 + << ", phi(alpha) = " << phi2 << "\n"; + if (iter > max_iter) + { + std::cout << "Max iterations reached!\n"; + // return alpha2; + + // return 0.0 indicating the linesearch failed to improve, + // so then Newton will terminate + return 0.0; + } + + 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) = " << phi2 << ") in " << iter + << " iterations!\n"; + return alpha2; +} + +Phi::Phi(std::function calcRes, + const mfem::Vector &state, + const mfem::Vector &descent_dir, + mfem::Vector &residual, + mfem::Operator &jac) + : calcRes(std::move(calcRes)), + state(state), + descent_dir(descent_dir), + scratch(state.Size()), + residual(residual), + phi0(residual.Norml2()), + dphi0( + [&]() + { + jac.Mult(descent_dir, scratch); + return -(scratch * residual) / phi0; + // return -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..54b6b60d --- /dev/null +++ b/src/common/linesearch.hpp @@ -0,0 +1,62 @@ +#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) = 0; + + virtual ~LineSearch() = default; +}; + +class BacktrackingLineSearch : public LineSearch +{ +public: + double search(const std::function &phi, + double phi0, + double dphi0, + 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 +class Phi +{ +public: + Phi(std::function calcRes, + const mfem::Vector &state, + const mfem::Vector &descent_dir, + mfem::Vector &residual, + mfem::Operator &jac); + + double operator()(double alpha); + +private: + 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 + +#endif diff --git a/src/common/mach_linearform.cpp b/src/common/mach_linearform.cpp index 6539bf57..5cbc8d9d 100644 --- a/src/common/mach_linearform.cpp +++ b/src/common/mach_linearform.cpp @@ -16,27 +16,11 @@ 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)) { - if (load.lf_fields) + if (load.lf_fields != nullptr) { auto it = load.lf_fields->find(name); if (it != load.lf_fields->end()) @@ -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 ea28f228..a972bd3d 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 @@ -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) @@ -164,7 +166,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_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/common/mach_residual.hpp b/src/common/mach_residual.hpp index 6776b2b1..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*/, @@ -67,7 +78,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 +115,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"); @@ -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/common/material_library.cpp b/src/common/material_library.cpp index 6a65ed90..3ef21bf5 100644 --- a/src/common/material_library.cpp +++ b/src/common/material_library.cpp @@ -4,342 +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}}, - {"H", - {0.0, - 8.993187962999999, - 10.978287105, - 83.247282676, - 211.230768762, - 458.336047457, - 1159.131573849, - 2773.494054824, - 1.342303920594017e6, - 2.675328620250853e6, - 3.9982409587815357e6}}, - {"mu_r", 750}, - {"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/common/matrix_operators.cpp b/src/common/matrix_operators.cpp index e2faf704..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)) @@ -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..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; } @@ -243,7 +244,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 b16ba750..5156f7f6 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; @@ -97,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_) { @@ -198,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, @@ -248,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_); } @@ -264,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]; } @@ -273,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"); @@ -295,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)); } @@ -323,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)); } @@ -354,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)); } @@ -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/mfem_extensions.hpp b/src/common/mfem_extensions.hpp index 5d2ca904..413f01ef 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; }; @@ -105,6 +107,14 @@ 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 @@ -112,7 +122,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 @@ -145,21 +155,20 @@ 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 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/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/common/relaxed_newton.cpp b/src/common/relaxed_newton.cpp new file mode 100644 index 00000000..1ddb1b39 --- /dev/null +++ b/src/common/relaxed_newton.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "mfem.hpp" +#include "nlohmann/json.hpp" + +#include "linesearch.hpp" + +#include "relaxed_newton.hpp" +#include "utils.hpp" + +namespace +{ + +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); + ls->max_iter = options.value("maxiter", ls->max_iter); + } + 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) + { + oper->Mult(x, res); + const bool have_b = (b.Size() == Height()); + if (have_b) + { + res -= b; + } + }; + + 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/src/physics/common_outputs.cpp b/src/physics/common_outputs.cpp index 668a837d..ebf55f33 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" @@ -10,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())); } } @@ -84,11 +72,40 @@ StateAverageFunctional::StateAverageFunctional( } } -AverageMagnitudeCurlState::AverageMagnitudeCurlState( - mfem::ParFiniteElementSpace &fes, - std::map &fields) - : AverageMagnitudeCurlState(fes, fields, {}) -{ } +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, @@ -110,31 +127,101 @@ 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")) { 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)); } } +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..9885eb12 100644 --- a/src/physics/common_outputs.hpp +++ b/src/physics/common_outputs.hpp @@ -12,56 +12,89 @@ 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); + } + + 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, @@ -69,6 +102,7 @@ class MassFunctional : public FunctionalOutput const nlohmann::json &options); private: + FunctionalOutput output; /// Density std::unique_ptr rho; }; @@ -133,6 +167,7 @@ class AverageMagnitudeCurlState friend void setInputs(AverageMagnitudeCurlState &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.state_integ, inputs); setInputs(output.volume, inputs); } @@ -145,8 +180,19 @@ 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); + std::map &fields) + : AverageMagnitudeCurlState(fes, fields, {}) + { } AverageMagnitudeCurlState(mfem::ParFiniteElementSpace &fes, std::map &fields, @@ -155,6 +201,8 @@ class AverageMagnitudeCurlState private: FunctionalOutput state_integ; FunctionalOutput volume; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; class IEAggregateFunctional @@ -175,6 +223,7 @@ class IEAggregateFunctional friend void setInputs(IEAggregateFunctional &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.numerator, inputs); setInputs(output.denominator, inputs); } @@ -182,11 +231,25 @@ 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; } + 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); @@ -194,6 +257,8 @@ class IEAggregateFunctional private: FunctionalOutput numerator; FunctionalOutput denominator; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; class IECurlMagnitudeAggregateFunctional @@ -214,6 +279,7 @@ class IECurlMagnitudeAggregateFunctional friend void setInputs(IECurlMagnitudeAggregateFunctional &output, const MachInputs &inputs) { + output.inputs = &inputs; setInputs(output.numerator, inputs); setInputs(output.denominator, inputs); } @@ -226,6 +292,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 +310,8 @@ class IECurlMagnitudeAggregateFunctional private: FunctionalOutput numerator; FunctionalOutput denominator; + MachInputs const *inputs = nullptr; + mfem::Vector scratch; }; } // namespace mach diff --git a/src/physics/electromagnetics/current_load.cpp b/src/physics/electromagnetics/current_load.cpp index 6be33cbb..62ddfcc0 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,15 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, scratch(&fes), // scratch(0), load(fes.GetTrueVSize()), + pcg(constructLinearSolver(fes.GetComm(), + {{"type", "gmres"}, + {"kdim", 500}, + // {{"type", "pcg"}, + {"reltol", 1e-12}, + {"abstol", 1e-12}, + {"maxiter", 500}, + {"printlevel", 2}}, + &amg)), div_free_proj(h1_fes, fes, h1_fes.GetElementTransformation(0)->OrderW() + @@ -201,6 +211,14 @@ CurrentLoad::CurrentLoad(adept::Stack &diff_stack, m_l_mesh_sens(new VectorFEMassIntegratorMeshSens), dirty(true) { + // amg.SetPrintLevel(0); + amg.SetMaxIter(5); + dynamic_cast(*pcg).SetPrintLevel( + IterativeSolver::PrintLevel().Warnings().Errors().Iterations()); + + /// project current coeff as initial guess for iterative solve + j.ProjectCoefficient(current); + /// Create a H(curl) mass matrix for integrating grid functions nd_mass.AddDomainIntegrator(new VectorFEMassIntegrator); @@ -225,8 +243,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 +252,17 @@ 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(); + // j.ProjectCoefficient(current); + // 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 +272,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 +285,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/physics/electromagnetics/current_source_functions.cpp b/src/physics/electromagnetics/current_source_functions.cpp index ea6c5e18..45e06f6e 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,66 @@ void box2CurrentSourceRevDiff(adept::Stack &diff_stack, source_jac.MultTranspose(V_bar, x_bar); } +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, + double J_bar, + mfem::Vector &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) +{ + 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, + double J_bar, + mfem::Vector &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) { team13_current(x.GetData(), J.GetData()); @@ -654,7 +732,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, @@ -689,7 +767,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, @@ -720,7 +798,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "x") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -743,7 +821,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "y") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -766,7 +844,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -789,7 +867,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "-z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -812,7 +890,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "ring") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -834,7 +912,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "box1") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -856,7 +934,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "box2") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -878,7 +956,7 @@ CurrentDensityCoefficient::CurrentDensityCoefficient( } else if (source == "team13") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { source_coeffs.emplace( attr, @@ -903,4 +981,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 (const 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 (const 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 (const 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 (const 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/electromag_integ.cpp b/src/physics/electromagnetics/electromag_integ.cpp index 162bb841..6d24952e 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" @@ -13,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, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -34,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, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double en = 0.0; @@ -63,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, 40); + const IntegrationRule *ir = &IntRules.Get(Geometry::Type::SEGMENT, 20); /// compute int_0^{B} \nuB dB double d2endB2 = 0.0; @@ -85,29 +86,28 @@ double calcMagneticEnergyDoubleDot(ElementTransformation &trans, return d2endB2; } -void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun, - Vector &elvect) +void NonlinearDiffusionIntegrator::AssembleElementVector( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elvect) { /// number of degrees of freedom int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; elvect.SetSize(ndof); - elvect = 0.0; + + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); #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 dshapedxt; #endif + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); - double b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, dimc); + double pointflux_buffer[3] = {}; + Vector pointflux(pointflux_buffer, space_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -116,49 +116,52 @@ void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { - return 2 * el.GetOrder(); + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; } }(); - ir = &IntRules.Get(el.GetGeomType(), order); + 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++) { - b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - double w = ip.weight / trans.Weight(); - w *= alpha; + double trans_weight = trans.Weight(); - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, 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(); + double w = alpha * ip.weight / trans_weight; - double model_val = model.Eval(trans, ip, b_mag); - model_val *= w; - b_vec *= model_val; + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); - curlshape_dFt.AddMult(b_vec, elvect); + 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 CurlCurlNLFIntegrator::AssembleElementGrad( +void NonlinearDiffusionIntegrator::AssembleElementGrad( const mfem::FiniteElement &el, mfem::ElementTransformation &trans, const mfem::Vector &elfun, @@ -166,22 +169,25 @@ void CurlCurlNLFIntegrator::AssembleElementGrad( { /// number of degrees of freedom int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; elmat.SetSize(ndof); elmat = 0.0; + int dim = el.GetDim(); + int space_dim = trans.GetSpaceDim(); + #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); - Vector scratch(ndof); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - scratch.SetSize(ndof); + 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 b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, dimc); + double pointflux_buffer[3] = {}; + Vector pointflux(pointflux_buffer, space_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -190,173 +196,87 @@ void CurlCurlNLFIntegrator::AssembleElementGrad( { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { - return 2 * el.GetOrder(); + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; } }(); - ir = &IntRules.Get(el.GetGeomType(), order); - } - - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - - trans.SetIntPoint(&ip); - - double w = ip.weight / trans.Weight(); - w *= alpha; - - if (dim == 3) + if (el.Space() == FunctionSpace::rQk) { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + ir = &RefinedIntRules.Get(el.GetGeomType(), order); } else { - el.CalcCurlShape(ip, curlshape_dFt); + ir = &IntRules.Get(el.GetGeomType(), order); } + } - /// calculate B = curl(A) - b_vec = 0.0; - curlshape_dFt.MultTranspose(elfun, b_vec); - b_vec /= trans.Weight(); - const double b_mag = b_vec.Norml2(); + elmat = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); - ///////////////////////////////////////////////////////////////////////// - /// calculate first term of Jacobian - ///////////////////////////////////////////////////////////////////////// + double trans_weight = trans.Weight(); - /// evaluate material model at ip - double model_val = model.Eval(trans, ip, b_mag); - /// multiply material value by integration weight - model_val *= w; - /// add first term to elmat - AddMult_a_AAt(model_val, curlshape_dFt, elmat); - // elmat.PrintMatlab(); std::cout << "\n"; + double w = alpha * ip.weight / trans_weight; - ///////////////////////////////////////////////////////////////////////// - /// calculate second term of Jacobian - ///////////////////////////////////////////////////////////////////////// - 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 - scratch = 0.0; - curlshape_dFt.Mult(b_vec, scratch); + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); - /// 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; + dshapedxt.MultTranspose(elfun, pointflux); - /// add second term to elmat - // AddMult_a_AAt(model_deriv, temp_matrix, elmat); - AddMult_a_VVt(model_deriv, scratch, elmat); + const double pointflux_norm = pointflux.Norml2(); - // for (int i = 0; i < ndof; ++i) - // { - // for (int j = 0; j < ndof; ++j) - // { - // try - // { - // if (!isfinite(elmat(i,j))) - // { - // throw MachException("nan!"); - // } - // } - // catch(const std::exception& e) - // { - // std::cerr << e.what() << '\n'; - // } - // } - // } - } - } -} + pointflux_norm_dot = 0.0; + dshapedxt.AddMult_a(1.0 / pointflux_norm, pointflux, pointflux_norm_dot); -void CurlCurlNLFIntegratorStateRevSens::AssembleRHSElementVect( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - mfem::Vector &state_bar) -{ - /// get the proper element, transformation, and state and adjoint vectors - int element = trans.ElementNo; - auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); - state.GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } + const double pointflux_mag = pointflux_norm / trans_weight; + pointflux_norm_dot /= trans_weight; - dof_tr = adjoint.FESpace()->GetElementVDofs(element, vdofs); - adjoint.GetSubVector(vdofs, psi); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(psi); - } + double model_val = model.Eval(trans, ip, pointflux_mag); - DenseMatrix elmat; - integ.AssembleElementGrad(el, trans, elfun, elmat); + double model_deriv = model.EvalStateDeriv(trans, ip, pointflux_mag); + pointflux_norm_dot *= model_deriv; - state_bar.SetSize(psi.Size()); - elmat.MultTranspose(psi, state_bar); -} + point_flux_2_dot = dshapedxt; + point_flux_2_dot *= model_val; -void CurlCurlNLFIntegratorStateFwdSens::AssembleRHSElementVect( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - mfem::Vector &res_dot) -{ - /// get the proper element, transformation, and state_dot vector - int element = trans.ElementNo; - auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); - state.GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } + if (abs(pointflux_norm) > 1e-14) + { + AddMultVWt(pointflux_norm_dot, pointflux, point_flux_2_dot); + } + point_flux_2_dot *= w; - dof_tr = state_dot.FESpace()->GetElementVDofs(element, vdofs); - state_dot.GetSubVector(vdofs, elfun_dot); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun_dot); + AddMultABt(dshapedxt, point_flux_2_dot, elmat); } - - DenseMatrix elmat; - integ.AssembleElementGrad(el, trans, elfun, elmat); - - res_dot.SetSize(elfun_dot.Size()); - elmat.Mult(elfun_dot, res_dot); } -void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( +void NonlinearDiffusionIntegratorMeshRevSens::AssembleRHSElementVect( const FiniteElement &mesh_el, ElementTransformation &mesh_trans, Vector &mesh_coords_bar) { - /// get the proper element, transformation, and state vector -#ifdef MFEM_THREAD_SAFE - mfem::Array vdofs; - mfem::Vector elfun, psi; -#endif - int element = mesh_trans.ElementNo; + const int element = mesh_trans.ElementNo; const auto &el = *state.FESpace()->GetFE(element); auto &trans = *state.FESpace()->GetElementTransformation(element); - const int ndof = mesh_el.GetDof(); - const int el_ndof = el.GetDof(); + const int mesh_ndof = mesh_el.GetDof(); + const int 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; + /// 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) @@ -372,31 +292,32 @@ void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( } #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(el_ndof, dimc), curlshape_dFt(el_ndof, 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 dshape; + DenseMatrix dshapedxt; + DenseMatrix dshapedxt_bar; + DenseMatrix PointMat_bar; #else - auto &curlshape = integ.curlshape; - auto &curlshape_dFt = integ.curlshape_dFt; - curlshape.SetSize(el_ndof, dimc); - curlshape_dFt.SetSize(el_ndof, 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); + auto &dshape = integ.dshape; + auto &dshapedxt = integ.dshapedxt; #endif - auto &nu = integ.model; - /// these vector's size is the spatial dimension we can stack allocate - double b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, dim); - double curl_psi_buffer[3]; + 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(trans); + auto &isotrans = dynamic_cast(mesh_trans); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -405,138 +326,138 @@ void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { - return 2 * el.GetOrder(); + // 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); - isotrans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - const double w = ip.weight / trans.Weight(); + double trans_weight = trans.Weight(); - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, isotrans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + /// holds quadrature weight + double w = alpha * ip.weight / trans_weight; - b_vec = 0.0; - curl_psi = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - curlshape_dFt.AddMultTranspose(psi, curl_psi); - const double curl_psi_dot_b = curl_psi * b_vec; + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); - const double b_vec_norm = b_vec.Norml2(); - const double b_mag = b_vec_norm / trans.Weight(); + dshapedxt.MultTranspose(elfun, pointflux); + dshapedxt.MultTranspose(psi, curl_psi); + const double curl_psi_dot_pointflux = curl_psi * pointflux; - const double nu_val = nu.Eval(isotrans, ip, b_mag); + 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 += nu_val * curl_psi_dot_b * w; + // fun += model_val * curl_psi_dot_pointflux * w; /// start reverse pass double fun_bar = 1.0; - /// fun += nu_val * curl_psi_dot_b * w; - double nu_val_bar = fun_bar * curl_psi_dot_b * w; - double curl_psi_dot_b_bar = fun_bar * nu_val * w; - double w_bar = fun_bar * nu_val * curl_psi_dot_b; + /// 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 nu_val = nu.Eval(isotrans, ip, b_mag); - double b_mag_bar = 0.0; - const double dnudb = nu.EvalStateDeriv(isotrans, ip, b_mag); - b_mag_bar += nu_val_bar * dnudb; + /// 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 b_mag = b_vec_norm / trans.Weight(); - double b_vec_norm_bar = 0.0; + /// const double pointflux_mag = pointflux_norm / trans_weight; + double pointflux_norm_bar = 0.0; double trans_weight_bar = 0.0; - 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(); - 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]; - Vector curl_psi_bar(curl_psi_bar_buffer, dim); + 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_b_bar, b_vec, curl_psi_bar); - add(b_vec_bar, curl_psi_dot_b_bar, curl_psi, b_vec_bar); - - curlshape_dFt_bar = 0.0; - /// curlshape_dFt.AddMultTranspose(psi, curl_psi); - AddMultVWt(curl_psi_bar, psi, curlshape_dFt_bar); - /// curlshape_dFt.AddMultTranspose(elfun, b_vec); - AddMultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + 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); - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - double jac_bar_buffer[9]; - DenseMatrix jac_bar(jac_bar_buffer, dim, dim); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_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); - /// const double w = ip.weight / trans.Weight(); - trans_weight_bar -= w_bar * ip.weight / pow(trans.Weight(), 2); + /// 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.WeightRevDiff(PointMat_bar); - PointMat_bar *= trans_weight_bar; + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); - isotrans.JacobianRevDiff(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 < ndof; ++j) + for (int j = 0; j < mesh_ndof; ++j) { - for (int d = 0; d < dimc; ++d) + for (int k = 0; k < curl_dim; ++k) { - mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); + mesh_coords_bar(k * mesh_ndof + j) += PointMat_bar(k, j); } } } } -void MagnetizationIntegrator::AssembleElementVector( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun, - Vector &elvect) +void MagnetizationSource2DIntegrator::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) { - // std::cout << "mag integ\n"; /// number of degrees of freedom int ndof = el.GetDof(); - int dim = el.GetDim(); + elvect.SetSize(ndof); - /// 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 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 curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc) mag_vec(dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - b_vec.SetSize(dimc); - mag_vec.SetSize(dimc); + DenseMatrix dshape; + DenseMatrix dshapedxt; + Vector scratch; #endif + dshape.SetSize(ndof, dim); + dshapedxt.SetSize(ndof, space_dim); + scratch.SetSize(ndof); - elvect.SetSize(ndof); + double mag_flux_buffer[3] = {}; + Vector mag_flux(mag_flux_buffer, space_dim); const IntegrationRule *ir = IntRule; if (ir == nullptr) @@ -549,683 +470,2543 @@ void MagnetizationIntegrator::AssembleElementVector( } else { - return 2 * el.GetOrder(); + // order = 2*el.GetOrder() - 2; // <-- this seems to work fine too + return 2 * el.GetOrder() + el.GetDim() - 1; } }(); - ir = &IntRules.Get(el.GetGeomType(), order); + 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); - /// holds quadrature weight - double w = alpha * ip.weight / trans.Weight(); + double w = alpha * ip.weight; - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - double nu_val = nu->Eval(trans, ip, b_vec.Norml2()); - nu_val *= w; + M.Eval(mag_flux, trans, ip); + mag_flux *= w; - mag_vec = 0.0; - mag->Eval(mag_vec, trans, ip); - mag_vec *= nu_val; + scratch = 0.0; + Vector grad_column; + dshapedxt.GetColumnReference(0, grad_column); + scratch.Add(mag_flux(1), grad_column); - curlshape_dFt.AddMult(mag_vec, elvect); + dshapedxt.GetColumnReference(1, grad_column); + scratch.Add(-mag_flux(0), grad_column); + + elvect += scratch; } } -void MagnetizationIntegrator::AssembleElementGrad( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun, - mfem::DenseMatrix &elmat) +void MagnetizationSource2DIntegratorMeshRevSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) { - elmat = 0.0; - /* - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); + const int element = mesh_trans.ElementNo; + const auto &el = *adjoint.FESpace()->GetFE(element); + auto &trans = *adjoint.FESpace()->GetElementTransformation(element); - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + 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; - /// holds quadrature weight - double w; + /// 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 curlshape(ndof,dimc), curlshape_dFt(ndof,dimc); - Vector b_vec(dimc), mag_vec(dimc), temp_vec(ndof), temp_vec2(ndof); + DenseMatrix dshape; + DenseMatrix dshapedxt; + DenseMatrix dshapedxt_bar; + DenseMatrix PointMat_bar; + Vector scratch_bar; #else - curlshape.SetSize(ndof,dimc); - curlshape_dFt.SetSize(ndof,dimc); - b_vec.SetSize(dimc); - mag_vec.SetSize(dimc); - temp_vec.SetSize(ndof); - temp_vec2.SetSize(ndof); + auto &dshape = integ.dshape; + auto &dshapedxt = integ.dshapedxt; #endif - elmat.SetSize(ndof); + 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 == NULL) + if (ir == nullptr) { - int order; - if (el.Space() == FunctionSpace::Pk) + int order = [&]() { - order = 2*el.GetOrder() - 2; + 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 { - order = 2*el.GetOrder(); + ir = &IntRules.Get(el.GetGeomType(), order); } - - ir = &IntRules.Get(el.GetGeomType(), order); } - elmat = 0.0; + 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); - w = ip.weight / trans.Weight(); - w *= alpha; + double w = alpha * ip.weight; - if ( dim == 3 ) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + el.CalcDShape(ip, dshape); + Mult(dshape, trans.AdjugateJacobian(), dshapedxt); - /// calculate B = curl(A) - b_vec = 0.0; - curlshape_dFt.MultTranspose(elfun, b_vec); - const double b_mag = b_vec.Norml2(); + M.Eval(mag_flux, trans, ip); + // mag_flux *= w; - 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 - temp_vec = 0.0; - curlshape_dFt.Mult(b_vec, temp_vec); - DenseMatrix temp_matrix(temp_vec.GetData(), ndof, 1); + Vector grad_column_0; + dshapedxt.GetColumnReference(0, grad_column_0); - mag_vec = 0.0; - mag->Eval(mag_vec, trans, ip); + Vector grad_column_1; + dshapedxt.GetColumnReference(1, grad_column_1); - temp_vec2 = 0.0; - curlshape_dFt.Mult(mag_vec, temp_vec2); - DenseMatrix temp_matrix2(temp_vec2.GetData(), ndof, 1); + // scratch = 0.0; + // add(mag_flux(1), grad_column_0, -mag_flux(0), grad_column_1, 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 - double nu_deriv = nu->EvalStateDeriv(trans, ip, b_mag); - nu_deriv *= w; - nu_deriv /= b_mag; + // const double psi_dot_scratch = psi * scratch; - AddMult_a_ABt(nu_deriv, temp_matrix2, temp_matrix, elmat); - } - } - */ -} + // elvect += scratch; + /// dummy functional for adjoint-weighted residual + // fun += psi_dot_scratch * w; -/** moved/replaced in mfem_common_integ.xpp -void VectorFECurldJdXIntegerator::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) -{ - /// get the proper element, transformation, and adjoint and m vector - Array adj_vdofs, state_vdofs; - Vector elfun, psi; - Vector elfun_proj; - int element = mesh_trans.ElementNo; + /// start reverse pass + double fun_bar = 1.0; - /// get the ND elements used for curl shape - const FiniteElement &nd_el = *adjoint->FESpace()->GetFE(element); - ElementTransformation &nd_trans = -*adjoint->FESpace()->GetElementTransformation(element); + /// fun += psi_dot_scratch * w; + double psi_dot_scratch_bar = fun_bar * w; + // double w_bar = fun_bar * psi_dot_scratch; - /// get the RT elements used for V shape - const FiniteElement &rt_el = *state->FESpace()->GetFE(element); - ElementTransformation &rt_trans = -*state->FESpace()->GetElementTransformation(element); + /// const double psi_dot_scratch = psi * scratch; + scratch_bar = 0.0; + scratch_bar.Add(psi_dot_scratch_bar, psi); - adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); - state->FESpace()->GetElementVDofs(element, state_vdofs); + /// 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); - const IntegrationRule *ir = NULL; - { - int order = rt_el.GetOrder() + nd_el.GetOrder() - 1; // <-- - ir = &IntRules.Get(rt_el.GetGeomType(), order); - } + /// M.Eval(mag_flux, trans, ip); + PointMat_bar = 0.0; + M.EvalRevDiff(mag_flux_bar, trans, ip, PointMat_bar); - elfun_proj.SetSize(state_vdofs.Size()); - rt_el.Project(*vec_coeff, rt_trans, elfun_proj); + /// 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); - state->GetSubVector(state_vdofs, elfun); - adjoint->GetSubVector(adj_vdofs, psi); + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); - Vector diff(elfun); - diff -= elfun_proj; + // 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); + } + } + } +} - int ndof = mesh_el.GetDof(); - int nd_ndof = nd_el.GetDof(); - int rt_ndof = rt_el.GetDof(); - int dim = nd_el.GetDim(); +void CurlCurlNLFIntegrator::AssembleElementVector(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elvect) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof*dimc); + elvect.SetSize(ndof); elvect = 0.0; + #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(nd_ndof,dimc), curlshape_dFt(nd_ndof,dimc); - DenseMatrix vshape(rt_ndof, dimc), vshape_dFt(rt_ndof, dimc); - Vector m_vec(dimc), m_hat(dimc), curl_psi(dimc), curl_psi_hat(dimc); + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + // Vector b_vec(dimc); #else - curlshape.SetSize(nd_ndof,dimc); - curlshape_dFt.SetSize(nd_ndof,dimc); - vshape.SetSize(rt_ndof, dimc); - vshape_dFt.SetSize(rt_ndof, dimc); - m_vec.SetSize(dimc); - m_hat.SetSize(dimc); - curl_psi.SetSize(dimc); - curl_psi_hat.SetSize(dimc); + curlshape.SetSize(ndof, dimc); + curlshape_dFt.SetSize(ndof, dimc); + // b_vec.SetSize(dimc); #endif - DenseMatrix PointMat_bar(dimc, ndof); - // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(nd_trans); + double b_vec_buffer[3] = {}; + Vector b_vec(b_vec_buffer, dimc); - for (int i = 0; i < ir->GetNPoints(); i++) + const IntegrationRule *ir = IntRule; + if (ir == nullptr) { - PointMat_bar = 0.0; - m_vec = 0.0; - m_hat = 0.0; - curl_psi_hat = 0.0; - curl_psi = 0.0; + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 1; + } + else + { + return 2 * el.GetOrder(); + } + }(); + + ir = &IntRules.Get(el.GetGeomType(), order); + } + for (int i = 0; i < ir->GetNPoints(); i++) + { + b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); - isotrans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - if ( dim == 3 ) - { - nd_el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, isotrans.Jacobian(), curlshape_dFt); + double w = ip.weight / trans.Weight(); + w *= alpha; - rt_el.CalcVShape(ip, vshape); - MultABt(vshape, isotrans.Jacobian(), vshape_dFt); + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - nd_el.CalcCurlShape(ip, curlshape_dFt); - rt_el.CalcVShape(ip, vshape_dFt); + el.CalcCurlShape(ip, curlshape_dFt); } - curlshape.AddMultTranspose(psi, curl_psi_hat); - curlshape_dFt.AddMultTranspose(psi, curl_psi); - vshape.AddMultTranspose(elfun, m_hat); - vshape_dFt.AddMultTranspose(elfun, m_vec); - - double nu_val = nu->Eval(isotrans, ip); - - double curl_psi_dot_m = curl_psi * m_vec; - - // nu * (\partial a^T b / \partial J) / |J| - DenseMatrix Jac_bar(3); - MultVWt(m_vec, curl_psi_hat, Jac_bar); - AddMultVWt(curl_psi, m_hat, Jac_bar); - Jac_bar *= nu_val / isotrans.Weight(); - - // (- nu * a^T b / |J|^2) * \partial |J| / \partial X - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= -nu_val * curl_psi_dot_m / pow(isotrans.Weight(), 2.0); - - isotrans.JacobianRevDiff(Jac_bar, PointMat_bar); + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans.Weight(); - // sensitivity with respect to the projection of the coefficient - if (vec_coeff) - { - Vector P_bar(rt_ndof); - vshape_dFt.Mult(curl_psi, P_bar); - P_bar *= 1 / isotrans.Weight(); - rt_el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); - } + double model_val = model.Eval(trans, ip, b_mag); + model_val *= w; + b_vec *= model_val; - for (int j = 0; j < ndof ; ++j) - { - for (int d = 0; d < dimc; ++d) - { - elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); - } - } + curlshape_dFt.AddMult(b_vec, elvect); } } -*/ -/** moved/replaced in mfem_common_integ.xpp -void VectorFEMassdJdXIntegerator::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +void CurlCurlNLFIntegrator::AssembleElementGrad( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::DenseMatrix &elmat) { - /// get the proper element, transformation, and adjoint and m vector - Array adj_vdofs, state_vdofs; - Vector elfun, psi; - int element = mesh_trans.ElementNo; + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elmat.SetSize(ndof); + elmat = 0.0; - /// get the ND elements used for adjoint and J - const FiniteElement &el = *adjoint->FESpace()->GetFE(element); - ElementTransformation &trans = -*adjoint->FESpace()->GetElementTransformation(element); +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); + Vector scratch(ndof); +#else + curlshape.SetSize(ndof, dimc); + curlshape_dFt.SetSize(ndof, dimc); + scratch.SetSize(ndof); +#endif - adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); - state->FESpace()->GetElementVDofs(element, state_vdofs); + double b_vec_buffer[3] = {}; + Vector b_vec(b_vec_buffer, dimc); const IntegrationRule *ir = IntRule; - if (ir == NULL) + if (ir == nullptr) { - // int order = 2 * el.GetOrder(); - int order = trans.OrderW() + 2 * el.GetOrder(); + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 1; + } + else + { + return 2 * el.GetOrder(); + } + }(); + ir = &IntRules.Get(el.GetGeomType(), order); } - state->GetSubVector(state_vdofs, elfun); - adjoint->GetSubVector(adj_vdofs, psi); - - int ndof = mesh_el.GetDof(); - int el_ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof*dimc); - elvect = 0.0; -#ifdef MFEM_THREAD_SAFE - DenseMatrix vshape(el_ndof, dimc), vshape_dFt(el_ndof, dimc); - Vector v_j_hat(dimc), v_j_vec(dimc), v_psi_hat(dimc), v_psi_vec(dimc); -#else - vshape.SetSize(el_ndof, dimc); - vshape_dFt.SetSize(el_ndof, dimc); - v_j_hat.SetSize(dimc); - v_j_vec.SetSize(dimc); - v_psi_hat.SetSize(dimc); - v_psi_vec.SetSize(dimc); -#endif - DenseMatrix PointMat_bar(dimc, ndof); - - // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(trans); - for (int i = 0; i < ir->GetNPoints(); i++) { - PointMat_bar = 0.0; - v_j_hat = 0.0; - v_j_vec = 0.0; - v_psi_hat = 0.0; - v_psi_vec = 0.0; - const IntegrationPoint &ip = ir->IntPoint(i); - isotrans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - if ( dim == 3 ) + double w = ip.weight / trans.Weight(); + w *= alpha; + + if (dim == 3) { - el.CalcVShape(ip, vshape); - Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - el.CalcVShape(ip, vshape_dFt); + el.CalcCurlShape(ip, curlshape_dFt); } - vshape.AddMultTranspose(psi, v_psi_hat); - vshape_dFt.AddMultTranspose(psi, v_psi_vec); - vshape.AddMultTranspose(elfun, v_j_hat); - vshape_dFt.AddMultTranspose(elfun, v_j_vec); - - double v_psi_dot_v_j = v_psi_vec * v_j_vec; - // nu * (\partial a^T b / \partial J) / |J| - DenseMatrix Jac_bar(3); - MultVWt(v_j_hat, v_psi_vec, Jac_bar); - AddMultVWt(v_psi_hat, v_j_vec, Jac_bar); - Jac_bar *= 1 / isotrans.Weight(); + /// calculate B = curl(A) + b_vec = 0.0; + curlshape_dFt.MultTranspose(elfun, b_vec); + b_vec /= trans.Weight(); + const double b_mag = b_vec.Norml2(); - // (- a^T b / |J|^2) * \partial |J| / \partial X - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= -v_psi_dot_v_j / pow(isotrans.Weight(), 2.0); + ///////////////////////////////////////////////////////////////////////// + /// calculate first term of Jacobian + ///////////////////////////////////////////////////////////////////////// - isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); + /// evaluate material model at ip + double model_val = model.Eval(trans, ip, b_mag); + /// multiply material value by integration weight + model_val *= w; + /// add first term to elmat + AddMult_a_AAt(model_val, curlshape_dFt, elmat); + // elmat.PrintMatlab(); std::cout << "\n"; - // sensitivity with respect to the projection of the coefficient - if (vec_coeff) + ///////////////////////////////////////////////////////////////////////// + /// calculate second term of Jacobian + ///////////////////////////////////////////////////////////////////////// + if (abs(b_mag) > 1e-14) { - Vector P_bar(el_ndof); - vshape_dFt.Mult(v_psi_vec, P_bar); - P_bar *= 1 / isotrans.Weight(); - el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); - } + /// 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); - for (int j = 0; j < ndof ; ++j) - { - for (int d = 0; d < dimc; ++d) - { - elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); - } - } - } -} -*/ + /// 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; -/** moved/replaced in mfem_common_integ.xpp + /// add second term to elmat + // AddMult_a_AAt(model_deriv, temp_matrix, elmat); + AddMult_a_VVt(model_deriv, scratch, elmat); + + // for (int i = 0; i < ndof; ++i) + // { + // for (int j = 0; j < ndof; ++j) + // { + // try + // { + // if (!isfinite(elmat(i,j))) + // { + // throw MachException("nan!"); + // } + // } + // catch(const std::exception& e) + // { + // std::cerr << e.what() << '\n'; + // } + // } + // } + } + } +} + +void CurlCurlNLFIntegratorStateRevSens::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &state_bar) +{ + /// get the proper element, transformation, and state and adjoint vectors + int element = trans.ElementNo; + 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); + } + + DenseMatrix elmat; + integ.AssembleElementGrad(el, trans, elfun, elmat); + + state_bar.SetSize(psi.Size()); + elmat.MultTranspose(psi, state_bar); +} + +void CurlCurlNLFIntegratorStateFwdSens::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &res_dot) +{ + /// get the proper element, transformation, and state_dot vector + int element = trans.ElementNo; + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + + dof_tr = state_dot.FESpace()->GetElementVDofs(element, vdofs); + state_dot.GetSubVector(vdofs, elfun_dot); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun_dot); + } + + DenseMatrix elmat; + integ.AssembleElementGrad(el, trans, elfun, elmat); + + res_dot.SetSize(elfun_dot.Size()); + elmat.Mult(elfun_dot, res_dot); +} + +void CurlCurlNLFIntegratorMeshRevSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + /// get the proper element, transformation, and state vector +#ifdef MFEM_THREAD_SAFE + mfem::Array vdofs; + mfem::Vector elfun, psi; +#endif + int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + 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; + + 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 curlshape(el_ndof, dimc), curlshape_dFt(el_ndof, 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); +#else + auto &curlshape = integ.curlshape; + auto &curlshape_dFt = integ.curlshape_dFt; + curlshape.SetSize(el_ndof, dimc); + curlshape_dFt.SetSize(el_ndof, 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 + auto &nu = integ.model; + + /// these vector's size is the spatial dimension we can stack allocate + double b_vec_buffer[3] = {}; + Vector b_vec(b_vec_buffer, dim); + double curl_psi_buffer[3] = {}; + Vector curl_psi(curl_psi_buffer, 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() - 1; + } + 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); + isotrans.SetIntPoint(&ip); + + const double w = ip.weight / trans.Weight(); + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, isotrans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + b_vec = 0.0; + curl_psi = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt.AddMultTranspose(psi, curl_psi); + const double curl_psi_dot_b = curl_psi * b_vec; + + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans.Weight(); + + const double nu_val = nu.Eval(isotrans, ip, b_mag); + + /// dummy functional for adjoint-weighted residual + // fun += nu_val * curl_psi_dot_b * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun += nu_val * curl_psi_dot_b * w; + double nu_val_bar = fun_bar * curl_psi_dot_b * w; + double curl_psi_dot_b_bar = fun_bar * nu_val * w; + double w_bar = fun_bar * nu_val * curl_psi_dot_b; + + /// double nu_val = nu.Eval(isotrans, ip, b_mag); + double b_mag_bar = 0.0; + const double dnudb = nu.EvalStateDeriv(isotrans, ip, b_mag); + b_mag_bar += nu_val_bar * dnudb; + + /// const double b_mag = b_vec_norm / trans.Weight(); + double b_vec_norm_bar = 0.0; + double trans_weight_bar = 0.0; + 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(); + 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] = {}; + 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); + add(b_vec_bar, curl_psi_dot_b_bar, curl_psi, b_vec_bar); + + curlshape_dFt_bar = 0.0; + /// curlshape_dFt.AddMultTranspose(psi, curl_psi); + AddMultVWt(curl_psi_bar, psi, curlshape_dFt_bar); + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + AddMultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + double jac_bar_buffer[9] = {}; + DenseMatrix jac_bar(jac_bar_buffer, dim, dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + + /// const double w = ip.weight / trans.Weight(); + trans_weight_bar -= w_bar * ip.weight / pow(trans.Weight(), 2); + + PointMat_bar = 0.0; + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= trans_weight_bar; + + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < ndof; ++j) + { + for (int d = 0; d < dimc; ++d) + { + mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); + } + } + } +} + +void MagnetizationIntegrator::AssembleElementVector( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun, + Vector &elvect) +{ + // std::cout << "mag integ\n"; + /// 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; + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + Vector b_vec(dimc) mag_vec(dimc); +#else + curlshape.SetSize(ndof, dimc); + curlshape_dFt.SetSize(ndof, dimc); + b_vec.SetSize(dimc); + mag_vec.SetSize(dimc); +#endif + + elvect.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); + } + + elvect = 0.0; + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + + trans.SetIntPoint(&ip); + + /// holds quadrature weight + double w = alpha * ip.weight / trans.Weight(); + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + double nu_val = nu->Eval(trans, ip, b_vec.Norml2()); + nu_val *= w; + + mag_vec = 0.0; + mag->Eval(mag_vec, trans, ip); + mag_vec *= nu_val; + + curlshape_dFt.AddMult(mag_vec, elvect); + } +} + +void MagnetizationIntegrator::AssembleElementGrad( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::DenseMatrix &elmat) +{ + elmat = 0.0; + /* + /// 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; + + /// holds quadrature weight + double w; + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof,dimc), curlshape_dFt(ndof,dimc); + Vector b_vec(dimc), mag_vec(dimc), temp_vec(ndof), temp_vec2(ndof); +#else + curlshape.SetSize(ndof,dimc); + curlshape_dFt.SetSize(ndof,dimc); + b_vec.SetSize(dimc); + mag_vec.SetSize(dimc); + temp_vec.SetSize(ndof); + temp_vec2.SetSize(ndof); +#endif + + elmat.SetSize(ndof); + + const IntegrationRule *ir = IntRule; + if (ir == NULL) + { + int order; + if (el.Space() == FunctionSpace::Pk) + { + order = 2*el.GetOrder() - 2; + } + else + { + order = 2*el.GetOrder(); + } + + 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); + + w = ip.weight / trans.Weight(); + w *= alpha; + + if ( dim == 3 ) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + /// calculate B = curl(A) + b_vec = 0.0; + curlshape_dFt.MultTranspose(elfun, b_vec); + const double b_mag = b_vec.Norml2(); + + 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 + temp_vec = 0.0; + curlshape_dFt.Mult(b_vec, temp_vec); + DenseMatrix temp_matrix(temp_vec.GetData(), ndof, 1); + + mag_vec = 0.0; + mag->Eval(mag_vec, trans, ip); + + temp_vec2 = 0.0; + 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 + /// defined by ip, and scale by integration point weight + double nu_deriv = nu->EvalStateDeriv(trans, ip, b_mag); + nu_deriv *= w; + nu_deriv /= b_mag; + + AddMult_a_ABt(nu_deriv, temp_matrix2, temp_matrix, elmat); + } + } + */ +} + +/** moved/replaced in mfem_common_integ.xpp +void VectorFECurldJdXIntegerator::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and adjoint and m vector + Array adj_vdofs, state_vdofs; + Vector elfun, psi; + Vector elfun_proj; + int element = mesh_trans.ElementNo; + + /// get the ND elements used for curl shape + const FiniteElement &nd_el = *adjoint->FESpace()->GetFE(element); + ElementTransformation &nd_trans = +*adjoint->FESpace()->GetElementTransformation(element); + + /// get the RT elements used for V shape + const FiniteElement &rt_el = *state->FESpace()->GetFE(element); + ElementTransformation &rt_trans = +*state->FESpace()->GetElementTransformation(element); + + adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + state->FESpace()->GetElementVDofs(element, state_vdofs); + + + + const IntegrationRule *ir = NULL; + { + int order = rt_el.GetOrder() + nd_el.GetOrder() - 1; // <-- + ir = &IntRules.Get(rt_el.GetGeomType(), order); + } + + elfun_proj.SetSize(state_vdofs.Size()); + rt_el.Project(*vec_coeff, rt_trans, elfun_proj); + + state->GetSubVector(state_vdofs, elfun); + adjoint->GetSubVector(adj_vdofs, psi); + + Vector diff(elfun); + diff -= elfun_proj; + + int ndof = mesh_el.GetDof(); + int nd_ndof = nd_el.GetDof(); + int rt_ndof = rt_el.GetDof(); + int dim = nd_el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof*dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(nd_ndof,dimc), curlshape_dFt(nd_ndof,dimc); + DenseMatrix vshape(rt_ndof, dimc), vshape_dFt(rt_ndof, dimc); + Vector m_vec(dimc), m_hat(dimc), curl_psi(dimc), curl_psi_hat(dimc); +#else + curlshape.SetSize(nd_ndof,dimc); + curlshape_dFt.SetSize(nd_ndof,dimc); + vshape.SetSize(rt_ndof, dimc); + vshape_dFt.SetSize(rt_ndof, dimc); + m_vec.SetSize(dimc); + m_hat.SetSize(dimc); + curl_psi.SetSize(dimc); + curl_psi_hat.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); + + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(nd_trans); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + PointMat_bar = 0.0; + m_vec = 0.0; + m_hat = 0.0; + curl_psi_hat = 0.0; + curl_psi = 0.0; + + const IntegrationPoint &ip = ir->IntPoint(i); + + isotrans.SetIntPoint(&ip); + + if ( dim == 3 ) + { + nd_el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, isotrans.Jacobian(), curlshape_dFt); + + rt_el.CalcVShape(ip, vshape); + MultABt(vshape, isotrans.Jacobian(), vshape_dFt); + } + else + { + nd_el.CalcCurlShape(ip, curlshape_dFt); + rt_el.CalcVShape(ip, vshape_dFt); + } + curlshape.AddMultTranspose(psi, curl_psi_hat); + curlshape_dFt.AddMultTranspose(psi, curl_psi); + vshape.AddMultTranspose(elfun, m_hat); + vshape_dFt.AddMultTranspose(elfun, m_vec); + + double nu_val = nu->Eval(isotrans, ip); + + double curl_psi_dot_m = curl_psi * m_vec; + + // nu * (\partial a^T b / \partial J) / |J| + DenseMatrix Jac_bar(3); + MultVWt(m_vec, curl_psi_hat, Jac_bar); + AddMultVWt(curl_psi, m_hat, Jac_bar); + Jac_bar *= nu_val / isotrans.Weight(); + + // (- nu * a^T b / |J|^2) * \partial |J| / \partial X + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= -nu_val * curl_psi_dot_m / pow(isotrans.Weight(), 2.0); + + isotrans.JacobianRevDiff(Jac_bar, PointMat_bar); + + // sensitivity with respect to the projection of the coefficient + if (vec_coeff) + { + Vector P_bar(rt_ndof); + vshape_dFt.Mult(curl_psi, P_bar); + P_bar *= 1 / isotrans.Weight(); + rt_el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); + } + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); + } + } + } +} +*/ + +/** moved/replaced in mfem_common_integ.xpp +void VectorFEMassdJdXIntegerator::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and adjoint and m vector + Array adj_vdofs, state_vdofs; + Vector elfun, psi; + int element = mesh_trans.ElementNo; + + /// get the ND elements used for adjoint and J + const FiniteElement &el = *adjoint->FESpace()->GetFE(element); + ElementTransformation &trans = +*adjoint->FESpace()->GetElementTransformation(element); + + adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + state->FESpace()->GetElementVDofs(element, state_vdofs); + + const IntegrationRule *ir = IntRule; + if (ir == NULL) + { + // int order = 2 * el.GetOrder(); + int order = trans.OrderW() + 2 * el.GetOrder(); + ir = &IntRules.Get(el.GetGeomType(), order); + } + + state->GetSubVector(state_vdofs, elfun); + adjoint->GetSubVector(adj_vdofs, psi); + + int ndof = mesh_el.GetDof(); + int el_ndof = el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof*dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix vshape(el_ndof, dimc), vshape_dFt(el_ndof, dimc); + Vector v_j_hat(dimc), v_j_vec(dimc), v_psi_hat(dimc), v_psi_vec(dimc); +#else + vshape.SetSize(el_ndof, dimc); + vshape_dFt.SetSize(el_ndof, dimc); + v_j_hat.SetSize(dimc); + v_j_vec.SetSize(dimc); + v_psi_hat.SetSize(dimc); + v_psi_vec.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); + + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(trans); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + PointMat_bar = 0.0; + v_j_hat = 0.0; + v_j_vec = 0.0; + v_psi_hat = 0.0; + v_psi_vec = 0.0; + + const IntegrationPoint &ip = ir->IntPoint(i); + + isotrans.SetIntPoint(&ip); + + if ( dim == 3 ) + { + el.CalcVShape(ip, vshape); + Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + } + else + { + el.CalcVShape(ip, vshape_dFt); + } + vshape.AddMultTranspose(psi, v_psi_hat); + vshape_dFt.AddMultTranspose(psi, v_psi_vec); + vshape.AddMultTranspose(elfun, v_j_hat); + vshape_dFt.AddMultTranspose(elfun, v_j_vec); + + double v_psi_dot_v_j = v_psi_vec * v_j_vec; + + // nu * (\partial a^T b / \partial J) / |J| + DenseMatrix Jac_bar(3); + MultVWt(v_j_hat, v_psi_vec, Jac_bar); + AddMultVWt(v_psi_hat, v_j_vec, Jac_bar); + Jac_bar *= 1 / isotrans.Weight(); + + // (- a^T b / |J|^2) * \partial |J| / \partial X + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= -v_psi_dot_v_j / pow(isotrans.Weight(), 2.0); + + isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); + + // sensitivity with respect to the projection of the coefficient + if (vec_coeff) + { + Vector P_bar(el_ndof); + vshape_dFt.Mult(v_psi_vec, P_bar); + P_bar *= 1 / isotrans.Weight(); + el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); + } + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); + } + } + } +} +*/ + +/** moved/replaced in mfem_common_integ.xpp void VectorFEWeakDivergencedJdXIntegrator::AssembleRHSElementVect( const FiniteElement &mesh_el, ElementTransformation &mesh_trans, Vector &elvect) { - /// get the proper element, transformation, and adjoint and m vector - Array adj_vdofs, state_vdofs; - Vector elfun, psi; + /// get the proper element, transformation, and adjoint and m vector + Array adj_vdofs, state_vdofs; + Vector elfun, psi; + int element = mesh_trans.ElementNo; + + /// get the H1 elements used for curl shape + const FiniteElement &h1_el = *adjoint->FESpace()->GetFE(element); + ElementTransformation &h1_trans = +*adjoint->FESpace()->GetElementTransformation(element); + + /// get the ND elements used for V shape + const FiniteElement &nd_el = *state->FESpace()->GetFE(element); + + adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + state->FESpace()->GetElementVDofs(element, state_vdofs); + + state->GetSubVector(state_vdofs, elfun); + adjoint->GetSubVector(adj_vdofs, psi); + + int ndof = mesh_el.GetDof(); + int h1_ndof = h1_el.GetDof(); + int nd_ndof = nd_el.GetDof(); + int dim = nd_el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + + const IntegrationRule *ir = NULL; + { + int order = (nd_el.Space() == FunctionSpace::Pk) ? + (nd_el.GetOrder() + h1_el.GetOrder() - 1) : + (nd_el.GetOrder() + h1_el.GetOrder() + 2*(dim-2)); + ir = &IntRules.Get(h1_el.GetGeomType(), order); + } + + elvect.SetSize(ndof*dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix dshape(h1_ndof,dimc), dshape_dFt(h1_ndof,dimc); + DenseMatrix vshape(nd_ndof, dimc), vshape_dFt(nd_ndof, dimc); + Vector v_vec(dimc), v_hat(dimc), d_psi(dimc), d_psi_hat(dimc); +#else + dshape.SetSize(h1_ndof,dimc); + dshape_dFt.SetSize(h1_ndof,dimc); + vshape.SetSize(nd_ndof, dimc); + vshape_dFt.SetSize(nd_ndof, dimc); + v_vec.SetSize(dimc); + v_hat.SetSize(dimc); + d_psi.SetSize(dimc); + d_psi_hat.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); + + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(h1_trans); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + PointMat_bar = 0.0; + v_vec = 0.0; + v_hat = 0.0; + d_psi_hat = 0.0; + d_psi = 0.0; + + const IntegrationPoint &ip = ir->IntPoint(i); + + isotrans.SetIntPoint(&ip); + + if ( dim == 3 ) + { + nd_el.CalcVShape(ip, vshape); + Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + } + else + { + nd_el.CalcVShape(ip, vshape_dFt); + } + h1_el.CalcDShape(ip, dshape); + Mult(dshape, isotrans.AdjugateJacobian(), dshape_dFt); + + dshape.AddMultTranspose(psi, d_psi_hat); + dshape_dFt.AddMultTranspose(psi, d_psi); + vshape.AddMultTranspose(elfun, v_hat); + vshape_dFt.AddMultTranspose(elfun, v_vec); + + double d_psi_dot_v = d_psi * v_vec; + + // (\partial a^T b / \partial J) / |J| + DenseMatrix Jac_bar(3); + MultVWt(d_psi_hat, v_vec, Jac_bar); + AddMultVWt(v_hat, d_psi, Jac_bar); + Jac_bar *= 1 / isotrans.Weight(); + + // (- a^T b / |J|^2) * \partial |J| / \partial X + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= -d_psi_dot_v / pow(isotrans.Weight(), 2.0); + + isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); + + // sensitivity with respect to the projection of the coefficient + if (vec_coeff) + { + Vector P_bar(nd_ndof); + vshape_dFt.Mult(d_psi, P_bar); + P_bar *= 1 / isotrans.Weight(); + nd_el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); + } + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + /// NOTE: this is -= instead of += since the weight is negated in + /// the original integrator (line 1312 in bilininteg.cpp) + elvect(d*ndof + j) -= alpha * ip.weight * PointMat_bar(d,j); + } + } + } +} +*/ + +/** moved/replaced in mfem_common_integ.xpp +void VectorFEDomainLFMeshSensInteg::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and adjoint and m vector + Array adj_vdofs, state_vdofs; + Vector elfun, psi; + int element = mesh_trans.ElementNo; + + /// get the ND elements used for adjoint and J + const FiniteElement &el = *adjoint->FESpace()->GetFE(element); + ElementTransformation &trans = +*adjoint->FESpace()->GetElementTransformation(element); + + adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + + const IntegrationRule *ir = IntRule; + if (ir == NULL) + { + // int order = 2 * el.GetOrder(); + int order = trans.OrderW() + 2 * el.GetOrder(); + ir = &IntRules.Get(el.GetGeomType(), order); + } + + adjoint->GetSubVector(adj_vdofs, psi); + + int ndof = mesh_el.GetDof(); + int el_ndof = el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof*dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix vshape(el_ndof, dimc), vshape_dFt(el_ndof, dimc); + Vector v_psi_vec(dimc); +#else + vshape.SetSize(el_ndof, dimc); + vshape_dFt.SetSize(el_ndof, dimc); + v_psi_vec.SetSize(dimc); + v_psi_hat.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); + Vector vec(dimc); + vec = 0.0; + + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(trans); + + for (int i = 0; i < ir->GetNPoints(); i++) + { + PointMat_bar = 0.0; + v_psi_vec = 0.0; + v_psi_hat = 0.0; + + const IntegrationPoint &ip = ir->IntPoint(i); + + isotrans.SetIntPoint(&ip); + + vec_coeff.Eval(vec, isotrans, ip); + + if ( dim == 3 ) + { + el.CalcVShape(ip, vshape); + Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + } + else + { + el.CalcVShape(ip, vshape_dFt); + } + vshape.AddMultTranspose(psi, v_psi_hat); + vshape_dFt.AddMultTranspose(psi, v_psi_vec); + + // \partial a^T b / \partial K + DenseMatrix Jac_bar(3); + MultVWt(v_psi_hat, vec, Jac_bar); + isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); + + // sensitivity with respect to the projection of the coefficient + vec_coeff.EvalRevDiff(v_psi_vec, isotrans, ip, PointMat_bar); + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); + } + } + } +} +*/ + +/** moved/replaced in mfem_common_integ.xpp +void GridFuncMeshSensIntegrator::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and adjoint and m vector + Array adj_vdofs; + Vector psi; + int element = mesh_trans.ElementNo; + + /// get the elements used the adjoint dofs + const FiniteElement &el = *adjoint->FESpace()->GetFE(element); + ElementTransformation &trans = +*adjoint->FESpace()->GetElementTransformation(element); + + adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + adjoint->GetSubVector(adj_vdofs, psi); + + int ndof = mesh_el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + + elvect.SetSize(ndof*dimc); + elvect = 0.0; + + DenseMatrix PointMat_bar(dimc, ndof); + + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(trans); + + PointMat_bar = 0.0; + el.ProjectRevDiff(psi, *vec_coeff, isotrans, PointMat_bar); + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + elvect(d*ndof + j) += alpha * PointMat_bar(d,j); + } + } +} +*/ + +double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + /// 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; +#endif + curlshape.SetSize(ndof, curl_dim); + curlshape_dFt.SetSize(ndof, curl_dim); + + double b_vec_buffer[3] = {}; + Vector b_vec(b_vec_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); + } + + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + /// holds quadrature weight + const 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; + } + return fun; +} + +void MagneticEnergyIntegrator::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; + +#ifdef MFEM_THREAD_SAFE + 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) + { + 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; + + 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); + + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + curlshape_dFt.AddMult(b_vec_bar, elfun_bar); + } +} + +void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) +{ + /// get the proper element, transformation, and state vector +#ifdef MFEM_THREAD_SAFE + Array vdofs; + Vector elfun; +#endif + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); + + const int ndof = mesh_el.GetDof(); + const int el_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); + } + + auto &nu = integ.nu; + +#ifdef MFEM_THREAD_SAFE + 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; +#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); + + 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(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) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + 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 energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // fun += energy * w; + + /// start reverse pass + double fun_bar = 1.0; + + /// fun += qp_en * w; + 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; + + 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(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); + + /// curlshape_dFt.AddMultTranspose(elfun, b_vec); + MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); + + /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + DenseMatrix jac_bar(curl_dim); + jac_bar = 0.0; + AddMult(curlshape_dFt_bar, curlshape, jac_bar); + + /// const double w = ip.weight * trans.Weight(); + trans_weight_bar += w_bar * ip.weight; + + PointMat_bar = 0.0; + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= trans_weight_bar; + + isotrans.JacobianRevDiff(jac_bar, PointMat_bar); + + // code to insert PointMat_bar into mesh_coords_bar; + for (int j = 0; j < ndof; ++j) + { + for (int d = 0; d < curl_dim; ++d) + { + mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); + } + } + */ + } +} + +/** commenting out co-energy stuff since I'm stopping maintaining it +double MagneticCoenergyIntegrator::GetElementEnergy( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + /// 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; + + /// holds quadrature weight + double w; + +#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); +#endif + + const IntegrationRule *ir = NULL; + const IntegrationRule *segment_ir = NULL; + if (ir == NULL) + { + int order; + if (el.Space() == FunctionSpace::Pk) + { + order = 2*el.GetOrder() - 2; + } + else + { + order = 2*el.GetOrder(); + } + + ir = &IntRules.Get(el.GetGeomType(), order); + } + + if (segment_ir == NULL) + { + // int order; + // if (el.Space() == FunctionSpace::Pk) + // { + // order = 2*el.GetOrder() - 2; + // } + // else + // { + // order = 2*el.GetOrder(); + // } + + // segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 2*(order+1)); + segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); + } + + double fun = 0.0; + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + + trans.SetIntPoint(&ip); + w = ip.weight; + + if ( dim == 3 ) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + b_vec /= trans.Weight(); + const double b_mag = b_vec.Norml2(); + const double nu_val = nu->Eval(trans, ip, b_mag); + + const double qp_en = integrateBH(segment_ir, trans, ip, + 0.0, nu_val * b_mag); + + fun += qp_en * w; + } + return fun; +} + +void MagneticCoenergyIntegrator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elvect) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + + elvect.SetSize(ndof); + elvect = 0.0; + + /// I believe this takes advantage of a 2D problem not having + /// a properly defined curl? Need more investigation + int dimc = (dim == 3) ? 3 : 1; + + /// holds quadrature weight + double w; + +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof,dimc), curlshape_dFt(ndof,dimc), M; + Vector b_vec(dimc), temp_vec(ndof); +#else + curlshape.SetSize(ndof,dimc); + curlshape_dFt.SetSize(ndof,dimc); + b_vec.SetSize(dimc); + temp_vec.SetSize(ndof); +#endif + + const IntegrationRule *ir = NULL; + const IntegrationRule *segment_ir = NULL; + { + int order; + if (el.Space() == FunctionSpace::Pk) + { + order = 2*el.GetOrder() - 2; + } + else + { + order = 2*el.GetOrder(); + } + + ir = &IntRules.Get(el.GetGeomType(), order); + } + /// TODO make segment's integration much higher than elements + { + // int order; + // if (el.Space() == FunctionSpace::Pk) + // { + // order = 2*el.GetOrder() - 2; + // } + // else + // { + // order = 2*el.GetOrder(); + // } + + // segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 2*(order+1)); + segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); + } + + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + + trans.SetIntPoint(&ip); + + w = ip.weight / trans.Weight(); + + if ( dim == 3 ) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + b_vec /= trans.Weight(); + const double b_mag = b_vec.Norml2(); + const double nu_val = nu->Eval(trans, ip, b_mag); + const double dnu_dB = nu->EvalStateDeriv(trans, ip, b_mag); + + /// temp_vec = curl(N_i) dot curl(A) + temp_vec = 0.0; + curlshape_dFt.Mult(b_vec, temp_vec); + double dwp_dh = RevADintegrateBH(segment_ir, trans, ip, + 0, nu_val * b_mag); + temp_vec *= dwp_dh*(dnu_dB + nu_val/b_mag); + temp_vec *= w; + elvect += temp_vec; + } +} + +double MagneticCoenergyIntegrator::integrateBH( + const IntegrationRule *ir, + ElementTransformation &trans, + const IntegrationPoint &old_ip, + double lower_bound, + double upper_bound) +{ + /// compute int_0^{\nu*B} \frac{H}{\nu} dH + double qp_en = 0.0; + for (int j = 0; j < ir->GetNPoints(); j++) + { + const IntegrationPoint &ip = ir->IntPoint(j); + double xi = ip.x * (upper_bound - lower_bound); + qp_en += ip.weight * xi / nu->Eval(trans, old_ip, xi); + } + qp_en *= (upper_bound - lower_bound); + return qp_en; +} + +double MagneticCoenergyIntegrator::FDintegrateBH( + const IntegrationRule *ir, + ElementTransformation &trans, + const IntegrationPoint &old_ip, + double lower_bound, + double upper_bound) +{ + double delta = 1e-5; + + 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); +} + +double MagneticCoenergyIntegrator::RevADintegrateBH( + const IntegrationRule *ir, + ElementTransformation &trans, + const IntegrationPoint &old_ip, + double lower_bound, + double upper_bound) +{ + /// compute int_0^{\nu*B} \frac{H}{\nu} dH + double qp_en = 0.0; + for (int j = 0; j < ir->GetNPoints(); j++) + { + const IntegrationPoint &ip = ir->IntPoint(j); + double xi = ip.x * (upper_bound - lower_bound); + qp_en += ip.weight * xi / nu->Eval(trans, old_ip, xi); + } + + /// insert forward code here to compute qp_en, but not the last part + /// where you multiply by (upper_bound - lower_bound) + /// start reverse mode for int_0^{\nu*B} \frac{H}{\nu} dH + // return qp_en*(upper_bound - lower_bound); + double upper_bound_bar = qp_en; + double qp_en_bar = (upper_bound - lower_bound); + for (int j = 0; j < ir->GetNPoints(); j++) + { + const IntegrationPoint &ip = ir->IntPoint(j); + 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)); + // double xi = ip.x * (upper_bound - lower_bound); + upper_bound_bar += ip.x*xi_bar; + } + return upper_bound_bar; +} + +void MagneticCoenergyIntegrator::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and state vector + Array vdofs; Vector elfun; int element = mesh_trans.ElementNo; + const FiniteElement *el = state.FESpace()->GetFE(element); + ElementTransformation *trans = +state.FESpace()->GetElementTransformation(element); + state.FESpace()->GetElementVDofs(element, vdofs); - /// get the H1 elements used for curl shape - const FiniteElement &h1_el = *adjoint->FESpace()->GetFE(element); - ElementTransformation &h1_trans = -*adjoint->FESpace()->GetElementTransformation(element); - - /// get the ND elements used for V shape - const FiniteElement &nd_el = *state->FESpace()->GetFE(element); + const IntegrationRule *ir = NULL; + const IntegrationRule *segment_ir = NULL; + { + int order; + if (el->Space() == FunctionSpace::Pk) + { + order = 2*el->GetOrder() - 2; + } + else + { + order = 2*el->GetOrder(); + } - adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); - state->FESpace()->GetElementVDofs(element, state_vdofs); + ir = &IntRules.Get(el->GetGeomType(), order); + } + /// TODO make segment's integration much higher than elements + { + int order; + if (el->Space() == FunctionSpace::Pk) + { + order = 2*el->GetOrder() - 2; + } + else + { + order = 2*el->GetOrder(); + } - state->GetSubVector(state_vdofs, elfun); - adjoint->GetSubVector(adj_vdofs, psi); + segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); + } + state.GetSubVector(vdofs, elfun); int ndof = mesh_el.GetDof(); - int h1_ndof = h1_el.GetDof(); - int nd_ndof = nd_el.GetDof(); - int dim = nd_el.GetDim(); + int el_ndof = el->GetDof(); + int dim = el->GetDim(); int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof*dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof,dimc), curlshape_dFt(ndof,dimc), M; + Vector b_vec(dimc), b_hat(dimc); +#else + curlshape.SetSize(el_ndof,dimc); + curlshape_dFt.SetSize(el_ndof,dimc); + b_vec.SetSize(dimc); + b_hat.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); - const IntegrationRule *ir = NULL; + // cast the ElementTransformation + IsoparametricTransformation &isotrans = + dynamic_cast(*trans); + + for (int i = 0; i < ir->GetNPoints(); i++) { - int order = (nd_el.Space() == FunctionSpace::Pk) ? - (nd_el.GetOrder() + h1_el.GetOrder() - 1) : - (nd_el.GetOrder() + h1_el.GetOrder() + 2*(dim-2)); - ir = &IntRules.Get(h1_el.GetGeomType(), order); + PointMat_bar = 0.0; + + const IntegrationPoint &ip = ir->IntPoint(i); + trans->SetIntPoint(&ip); + // double w = ip.weight / trans->Weight(); + if ( dim == 3 ) + { + el->CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans->Jacobian(), curlshape_dFt); + } + else + { + el->CalcCurlShape(ip, curlshape_dFt); + } + + b_hat = 0.0; + b_vec = 0.0; + curlshape.AddMultTranspose(elfun, b_hat); + curlshape_dFt.AddMultTranspose(elfun, b_vec); + b_vec /= trans->Weight(); + const double b_mag = b_vec.Norml2(); + + const double nu_val = nu->Eval(*trans, ip, b_mag); + const double dnu_dB = nu->EvalStateDeriv(*trans, ip, b_mag); + + const double wp = integrateBH(segment_ir, *trans, ip, + 0.0, nu_val * b_mag); + + // start reverse sweep + const double dwp_dh = RevADintegrateBH(segment_ir, *trans, ip, + 0.0, nu_val * b_mag); + + DenseMatrix BB_hatT(3); + MultVWt(b_vec, b_hat, BB_hatT); + BB_hatT *= dwp_dh*(dnu_dB + nu_val/b_mag); // / trans->Weight(); + + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= -wp / pow(trans->Weight(), 2.0); + isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); + + for (int j = 0; j < ndof ; ++j) + { + for (int d = 0; d < dimc; ++d) + { + elvect(d*ndof + j) += ip.weight * PointMat_bar(d,j); + } + } } +} +*/ - elvect.SetSize(ndof*dimc); +double BNormIntegrator::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + /// 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; + +#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); +#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); + } + + 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); + + /// holds quadrature weight + auto w = ip.weight * trans.Weight(); + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + curlshape_dFt.AddMultTranspose(elfun, b_vec); + const double b_mag = b_vec.Norml2() / trans.Weight(); + fun += b_mag * w; + } + return fun; +} + +void BNormIntegrator::AssembleElementVector(const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elvect) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); + + elvect.SetSize(ndof); elvect = 0.0; + + /// I believe this takes advantage of a 2D problem not having + /// a properly defined curl? Need more investigation + int dimc = (dim == 3) ? 3 : 1; + #ifdef MFEM_THREAD_SAFE - DenseMatrix dshape(h1_ndof,dimc), dshape_dFt(h1_ndof,dimc); - DenseMatrix vshape(nd_ndof, dimc), vshape_dFt(nd_ndof, dimc); - Vector v_vec(dimc), v_hat(dimc), d_psi(dimc), d_psi_hat(dimc); + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + Vector b_vec(dimc), temp_vec(ndof); #else - dshape.SetSize(h1_ndof,dimc); - dshape_dFt.SetSize(h1_ndof,dimc); - vshape.SetSize(nd_ndof, dimc); - vshape_dFt.SetSize(nd_ndof, dimc); - v_vec.SetSize(dimc); - v_hat.SetSize(dimc); - d_psi.SetSize(dimc); - d_psi_hat.SetSize(dimc); + curlshape.SetSize(ndof, dimc); + curlshape_dFt.SetSize(ndof, dimc); + b_vec.SetSize(dimc); + temp_vec.SetSize(ndof); #endif - DenseMatrix PointMat_bar(dimc, ndof); - // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(h1_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++) { - PointMat_bar = 0.0; - v_vec = 0.0; - v_hat = 0.0; - d_psi_hat = 0.0; - d_psi = 0.0; - + b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); - isotrans.SetIntPoint(&ip); + trans.SetIntPoint(&ip); - if ( dim == 3 ) + /// holds quadrature weight + auto w = ip.weight / trans.Weight(); + + if (dim == 3) { - nd_el.CalcVShape(ip, vshape); - Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } else { - nd_el.CalcVShape(ip, vshape_dFt); + el.CalcCurlShape(ip, curlshape_dFt); } - h1_el.CalcDShape(ip, dshape); - Mult(dshape, isotrans.AdjugateJacobian(), dshape_dFt); - dshape.AddMultTranspose(psi, d_psi_hat); - dshape_dFt.AddMultTranspose(psi, d_psi); - vshape.AddMultTranspose(elfun, v_hat); - vshape_dFt.AddMultTranspose(elfun, v_vec); + curlshape_dFt.AddMultTranspose(elfun, b_vec); + double b_mag = b_vec.Norml2() / trans.Weight(); - double d_psi_dot_v = d_psi * v_vec; + /// temp_vec = curl(N_i) dot curl(A) + temp_vec = 0.0; + curlshape_dFt.Mult(b_vec, temp_vec); + temp_vec /= b_mag; + temp_vec *= w; + elvect += temp_vec; + } +} - // (\partial a^T b / \partial J) / |J| - DenseMatrix Jac_bar(3); - MultVWt(d_psi_hat, v_vec, Jac_bar); - AddMultVWt(v_hat, d_psi, Jac_bar); - Jac_bar *= 1 / isotrans.Weight(); +double BNormSquaredIntegrator::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) +{ + /// number of degrees of freedom + int ndof = el.GetDof(); + int dim = el.GetDim(); - // (- a^T b / |J|^2) * \partial |J| / \partial X - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= -d_psi_dot_v / pow(isotrans.Weight(), 2.0); + /// I believe this takes advantage of a 2D problem not having + /// a properly defined curl? Need more investigation + int dimc = (dim == 3) ? 3 : 1; - isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); +#else + curlshape.SetSize(ndof, dimc); + curlshape_dFt.SetSize(ndof, dimc); +#endif - // sensitivity with respect to the projection of the coefficient - if (vec_coeff) - { - Vector P_bar(nd_ndof); - vshape_dFt.Mult(d_psi, P_bar); - P_bar *= 1 / isotrans.Weight(); - nd_el.ProjectRevDiff(P_bar, *vec_coeff, isotrans, PointMat_bar); - } + double b_vec_buffer[3] = {}; + Vector b_vec(b_vec_buffer, dimc); - for (int j = 0; j < ndof ; ++j) + const IntegrationRule *ir = IntRule; + if (ir == nullptr) + { + int order = [&]() { - for (int d = 0; d < dimc; ++d) + if (el.Space() == FunctionSpace::Pk) { - /// NOTE: this is -= instead of += since the weight is negated in - /// the original integrator (line 1312 in bilininteg.cpp) - elvect(d*ndof + j) -= alpha * ip.weight * PointMat_bar(d,j); + 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); + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); } + + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + + auto trans_weight = trans.Weight(); + const double b_mag = b_vec.Norml2() / trans_weight; + fun += b_mag * b_mag * ip.weight * trans_weight; } + return fun; } -*/ -/** moved/replaced in mfem_common_integ.xpp -void VectorFEDomainLFMeshSensInteg::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +void BNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) { - /// get the proper element, transformation, and adjoint and m vector - Array adj_vdofs, state_vdofs; - Vector elfun, psi; + /// get the proper element, transformation, and state vector + Array vdofs; + Vector elfun; int element = mesh_trans.ElementNo; - - /// get the ND elements used for adjoint and J - const FiniteElement &el = *adjoint->FESpace()->GetFE(element); - ElementTransformation &trans = -*adjoint->FESpace()->GetElementTransformation(element); - - adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); + const FiniteElement &el = *state.FESpace()->GetFE(element); + ElementTransformation *trans = + state.FESpace()->GetElementTransformation(element); const IntegrationRule *ir = IntRule; - if (ir == NULL) + if (ir == nullptr) { - // int order = 2 * el.GetOrder(); - int order = trans.OrderW() + 2 * el.GetOrder(); + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); + ir = &IntRules.Get(el.GetGeomType(), order); } - adjoint->GetSubVector(adj_vdofs, psi); + auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } int ndof = mesh_el.GetDof(); int el_ndof = el.GetDof(); int dim = el.GetDim(); int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof*dimc); + elvect.SetSize(ndof * dimc); elvect = 0.0; #ifdef MFEM_THREAD_SAFE - DenseMatrix vshape(el_ndof, dimc), vshape_dFt(el_ndof, dimc); - Vector v_psi_vec(dimc); + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + Vector b_vec(dimc); #else - vshape.SetSize(el_ndof, dimc); - vshape_dFt.SetSize(el_ndof, dimc); - v_psi_vec.SetSize(dimc); - v_psi_hat.SetSize(dimc); + curlshape.SetSize(el_ndof, dimc); + curlshape_dFt.SetSize(el_ndof, dimc); + b_vec.SetSize(dimc); #endif DenseMatrix PointMat_bar(dimc, ndof); - Vector vec(dimc); - vec = 0.0; + Vector b_hat(dimc); // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(trans); + auto &isotrans = dynamic_cast(*trans); for (int i = 0; i < ir->GetNPoints(); i++) { - PointMat_bar = 0.0; - v_psi_vec = 0.0; - v_psi_hat = 0.0; - const IntegrationPoint &ip = ir->IntPoint(i); - - isotrans.SetIntPoint(&ip); - - vec_coeff.Eval(vec, isotrans, ip); - - if ( dim == 3 ) + trans->SetIntPoint(&ip); + double w = ip.weight; + if (dim == 3) { - el.CalcVShape(ip, vshape); - Mult(vshape, isotrans.AdjugateJacobian(), vshape_dFt); + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans->Jacobian(), curlshape_dFt); } else { - el.CalcVShape(ip, vshape_dFt); + el.CalcCurlShape(ip, curlshape_dFt); } - vshape.AddMultTranspose(psi, v_psi_hat); - vshape_dFt.AddMultTranspose(psi, v_psi_vec); - // \partial a^T b / \partial K - DenseMatrix Jac_bar(3); - MultVWt(v_psi_hat, vec, Jac_bar); - isotrans.AdjugateJacobianRevDiff(Jac_bar, PointMat_bar); + b_vec = 0.0; + curlshape_dFt.AddMultTranspose(elfun, b_vec); + // start reverse sweep + PointMat_bar = 0.0; - // sensitivity with respect to the projection of the coefficient - vec_coeff.EvalRevDiff(v_psi_vec, isotrans, ip, PointMat_bar); + /** the following computes `\partial (||B||/|J|) / \partial X` + * This is useful elsewhere, but for the test to pass we need + * `\partial (||B||/|J|)*(w*|J|) / \partial X`... - for (int j = 0; j < ndof ; ++j) + double weight_bar = -b_vec.Norml2() / pow(trans->Weight(), 2.0); + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= weight_bar; + + b_hat = 0.0; + curlshape.AddMultTranspose(elfun, b_hat); + DenseMatrix BB_hatT(3); + MultVWt(b_vec, b_hat, BB_hatT); + BB_hatT *= 1.0 / (trans->Weight() * b_vec.Norml2()); + isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); + */ + + b_hat = 0.0; + curlshape.AddMultTranspose(elfun, b_hat); + DenseMatrix BB_hatT(3); + MultVWt(b_vec, b_hat, BB_hatT); + BB_hatT *= 1.0 / b_vec.Norml2(); + isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); + + // code to insert PointMat_bar into elvect; + for (int j = 0; j < ndof; ++j) { for (int d = 0; d < dimc; ++d) { - elvect(d*ndof + j) += alpha * ip.weight * PointMat_bar(d,j); + elvect(d * ndof + j) += w * PointMat_bar(d, j); } } } } -*/ -/** moved/replaced in mfem_common_integ.xpp -void GridFuncMeshSensIntegrator::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +double nuBNormIntegrator::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { - /// get the proper element, transformation, and adjoint and m vector - Array adj_vdofs; - Vector psi; - int element = mesh_trans.ElementNo; - - /// get the elements used the adjoint dofs - const FiniteElement &el = *adjoint->FESpace()->GetFE(element); - ElementTransformation &trans = -*adjoint->FESpace()->GetElementTransformation(element); - - adjoint->FESpace()->GetElementVDofs(element, adj_vdofs); - adjoint->GetSubVector(adj_vdofs, psi); - - int ndof = mesh_el.GetDof(); + /// 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; - elvect.SetSize(ndof*dimc); - elvect = 0.0; +#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); +#endif - DenseMatrix PointMat_bar(dimc, 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(); + } + }(); - // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(trans); + ir = &IntRules.Get(el.GetGeomType(), order); + } - PointMat_bar = 0.0; - el.ProjectRevDiff(psi, *vec_coeff, isotrans, PointMat_bar); + double fun = 0.0; - for (int j = 0; j < ndof ; ++j) + for (int i = 0; i < ir->GetNPoints(); i++) { - for (int d = 0; d < dimc; ++d) + b_vec = 0.0; + const IntegrationPoint &ip = ir->IntPoint(i); + + trans.SetIntPoint(&ip); + + /// holds quadrature weight + auto w = ip.weight / trans.Weight(); + + if (dim == 3) { - elvect(d*ndof + j) += alpha * PointMat_bar(d,j); + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + curlshape_dFt.AddMultTranspose(elfun, b_vec); + fun += nu->Eval(trans, ip, b_vec.Norml2()) * b_vec.Norml2() * w; } + return fun; } -*/ -double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +void nuBNormIntegrator::AssembleElementVector( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun, + mfem::Vector &elvect) { /// number of degrees of freedom int ndof = el.GetDof(); int dim = el.GetDim(); + elvect.SetSize(ndof); + elvect = 0.0; + /// I believe this takes advantage of a 2D problem not having /// a properly defined curl? Need more investigation int dimc = (dim == 3) ? 3 : 1; #ifdef MFEM_THREAD_SAFE DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); + Vector b_vec(dimc), temp_vec(ndof); #else curlshape.SetSize(ndof, dimc); curlshape_dFt.SetSize(ndof, dimc); b_vec.SetSize(dimc); + temp_vec.SetSize(ndof); #endif const IntegrationRule *ir = IntRule; @@ -1246,41 +3027,192 @@ double MagneticEnergyIntegrator::GetElementEnergy(const FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } - 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); - /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + auto w = ip.weight / trans.Weight(); + + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + + curlshape_dFt.AddMultTranspose(elfun, b_vec); + double b_mag = b_vec.Norml2(); + + /// temp_vec = curl(N_i) dot curl(A) + temp_vec = 0.0; + curlshape_dFt.Mult(b_vec, temp_vec); + double nu_val = nu->Eval(trans, ip, b_mag); + double dnu_dB = nu->EvalStateDeriv(trans, ip, b_mag); + temp_vec *= (dnu_dB + nu_val / b_mag); + temp_vec *= w; + elvect += temp_vec; + } +} + +void nuBNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and state vector + Array vdofs; + Vector elfun; + int element = mesh_trans.ElementNo; + const FiniteElement &el = *state.FESpace()->GetFE(element); + ElementTransformation *trans = + state.FESpace()->GetElementTransformation(element); + + 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 *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); + state.GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) + { + dof_tr->InvTransformPrimal(elfun); + } + + int ndof = mesh_el.GetDof(); + int el_ndof = el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof * dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + Vector b_vec(dimc); +#else + curlshape.SetSize(el_ndof, dimc); + curlshape_dFt.SetSize(el_ndof, dimc); + b_vec.SetSize(dimc); +#endif + // DenseMatrix PointMat_bar(dimc, ndof); + DenseMatrix PointMat_bar_1(dimc, ndof); + DenseMatrix PointMat_bar_2(dimc, ndof); + DenseMatrix PointMat_bar_3(dimc, ndof); + // Vector DofVal(elfun.Size()); + + Vector b_hat(dimc); + + // cast the ElementTransformation + auto &isotrans = dynamic_cast(*trans); + + for (int i = 0; i < ir->GetNPoints(); ++i) + { + PointMat_bar_1 = 0.0; + PointMat_bar_2 = 0.0; + PointMat_bar_3 = 0.0; + b_vec = 0.0; + b_hat = 0.0; + const IntegrationPoint &ip = ir->IntPoint(i); + + trans->SetIntPoint(&ip); + if (dim == 3) + { + el.CalcCurlShape(ip, curlshape); + MultABt(curlshape, trans->Jacobian(), curlshape_dFt); + } + else + { + el.CalcCurlShape(ip, curlshape_dFt); + } + curlshape.AddMultTranspose(elfun, b_hat); + curlshape_dFt.AddMultTranspose(elfun, b_vec); + + double nu_val = nu->Eval(*trans, ip, b_vec.Norml2()); + double nu_deriv = nu->EvalStateDeriv(*trans, ip, b_vec.Norml2()); + + Vector dNormBdB(b_vec); + dNormBdB /= b_vec.Norml2(); + DenseMatrix dBdJ(b_hat.Size(), b_vec.Size()); + MultVWt(dNormBdB, b_hat, dBdJ); + isotrans.JacobianRevDiff(dBdJ, PointMat_bar_1); + PointMat_bar_1 *= nu_val / isotrans.Weight(); + + isotrans.WeightRevDiff(PointMat_bar_2); + PointMat_bar_2 *= -nu_val * b_vec.Norml2() / pow(isotrans.Weight(), 2); + + isotrans.JacobianRevDiff(dBdJ, PointMat_bar_3); + PointMat_bar_3 *= b_vec.Norml2() * nu_deriv / isotrans.Weight(); + // 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(); + // if ( dim == 3 ) + // { + // el->CalcCurlShape(ip, curlshape); + // MultABt(curlshape, trans->Jacobian(), curlshape_dFt); + // } + // else + // { + // el->CalcCurlShape(ip, curlshape_dFt); + // } + // curlshape_dFt.AddMultTranspose(elfun, b_vec); + // // start reverse sweep + + // PointMat_bar = 0.0; + // // fun += b_vec.Norml2() * w; + // Vector b_vec_bar(b_vec); + // b_vec_bar *= w / b_vec.Norml2(); + // double w_bar = b_vec.Norml2(); + // // curlshape_dFt.AddMultTranspose(elfun, b_vec); + // DenseMatrix curlshape_dFt_bar(elfun.Size(), b_vec_bar.Size()); + // MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); + // // MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + // DenseMatrix Jac_bar(3); + // MultAtB(curlshape_dFt_bar, curlshape, Jac_bar); + // // w = ip.weight / trans.Weight(); + // double weight_bar = -w_bar*ip.weight/pow(trans->Weight(), 2.0); + // 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); + // // code to insert PointMat_bar into elvect; - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else + for (int j = 0; j < ndof; ++j) { - el.CalcCurlShape(ip, curlshape_dFt); + for (int d = 0; d < dimc; ++d) + { + elvect(d * ndof + j) += + ip.weight * (PointMat_bar_1(d, j) + PointMat_bar_2(d, j) + + PointMat_bar_3(d, j)); + // elvect(d*ndof + j) += PointMat_bar(d,j); + } } - - 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; } - return fun; } -void MagneticEnergyIntegrator::AssembleElementVector( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun, - Vector &elvect) +double nuFuncIntegrator::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { /// number of degrees of freedom int ndof = el.GetDof(); @@ -1299,7 +3231,7 @@ void MagneticEnergyIntegrator::AssembleElementVector( b_vec.SetSize(dimc); #endif - const IntegrationRule *ir = IntRule; + const IntegrationRule *ir = mfem::NonlinearFormIntegrator::IntRule; if (ir == nullptr) { int order = [&]() @@ -1317,15 +3249,16 @@ void MagneticEnergyIntegrator::AssembleElementVector( ir = &IntRules.Get(el.GetGeomType(), order); } - elvect.SetSize(ndof); - elvect = 0.0; + 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); - /// holds quadrature weight - const double w = ip.weight / trans.Weight(); + auto w = ip.weight / trans.Weight(); if (dim == 3) { @@ -1337,72 +3270,27 @@ void MagneticEnergyIntegrator::AssembleElementVector( el.CalcCurlShape(ip, curlshape_dFt); } - b_vec = 0.0; curlshape_dFt.AddMultTranspose(elfun, b_vec); const double b_mag = b_vec.Norml2() / trans.Weight(); - - const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); - - b_vec *= energy_dot * w / b_mag; - curlshape_dFt.AddMult(b_vec, elvect); + const double nu_val = nu->Eval(trans, ip, b_mag); + fun += nu_val * w; } + return fun; } -void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &mesh_coords_bar) +void nuFuncIntegrator::AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &elvect) { - auto &nu = integ.nu; - /// get the proper element, transformation, and state vector -#ifdef MFEM_THREAD_SAFE Array vdofs; Vector elfun; -#endif - const int element = mesh_trans.ElementNo; - const auto &el = *state.FESpace()->GetFE(element); - auto &trans = *state.FESpace()->GetElementTransformation(element); - - 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; - - 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(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); -#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 - - // cast the ElementTransformation - auto &isotrans = dynamic_cast(trans); + int element = mesh_trans.ElementNo; + const FiniteElement &el = *state->FESpace()->GetFE(element); + ElementTransformation *trans = + state->FESpace()->GetElementTransformation(element); - const IntegrationRule *ir = IntRule; + const IntegrationRule *ir = mfem::NonlinearFormIntegrator::IntRule; if (ir == nullptr) { int order = [&]() @@ -1420,489 +3308,282 @@ void MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect( ir = &IntRules.Get(el.GetGeomType(), order); } - 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(); - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - 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 energy = calcMagneticEnergy(trans, ip, nu, b_mag); - // fun += energy * w; - - /// start reverse pass - double fun_bar = 1.0; - - /// fun += qp_en * w; - 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); - - Vector b_vec_bar(dimc); - 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); - MultVWt(b_vec_bar, elfun, curlshape_dFt_bar); - - /// MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - DenseMatrix jac_bar(dimc); - jac_bar = 0.0; - AddMult(curlshape_dFt_bar, curlshape, jac_bar); - - /// const double w = ip.weight * trans.Weight(); - trans_weight_bar += w_bar * ip.weight; - - PointMat_bar = 0.0; - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= trans_weight_bar; - - isotrans.JacobianRevDiff(jac_bar, PointMat_bar); - - // code to insert PointMat_bar into mesh_coords_bar; - for (int j = 0; j < ndof; ++j) - { - for (int d = 0; d < dimc; ++d) - { - mesh_coords_bar(d * ndof + j) += PointMat_bar(d, j); - } - } - } -} - -/** commenting out co-energy stuff since I'm stopping maintaining it -double MagneticCoenergyIntegrator::GetElementEnergy( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) -{ - /// 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; - - /// holds quadrature weight - double w; - -#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); -#endif - - const IntegrationRule *ir = NULL; - const IntegrationRule *segment_ir = NULL; - if (ir == NULL) - { - int order; - if (el.Space() == FunctionSpace::Pk) - { - order = 2*el.GetOrder() - 2; - } - else - { - order = 2*el.GetOrder(); - } - - ir = &IntRules.Get(el.GetGeomType(), order); - } - - if (segment_ir == NULL) + auto *dof_tr = state->FESpace()->GetElementVDofs(element, vdofs); + state->GetSubVector(vdofs, elfun); + if (dof_tr != nullptr) { - // int order; - // if (el.Space() == FunctionSpace::Pk) - // { - // order = 2*el.GetOrder() - 2; - // } - // else - // { - // order = 2*el.GetOrder(); - // } - - // segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 2*(order+1)); - segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); + dof_tr->InvTransformPrimal(elfun); } - double fun = 0.0; + int ndof = mesh_el.GetDof(); + int el_ndof = el.GetDof(); + int dim = el.GetDim(); + int dimc = (dim == 3) ? 3 : 1; + elvect.SetSize(ndof * dimc); + elvect = 0.0; +#ifdef MFEM_THREAD_SAFE + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; + Vector b_vec(dimc); +#else + curlshape.SetSize(el_ndof, dimc); + curlshape_dFt.SetSize(el_ndof, dimc); + b_vec.SetSize(dimc); +#endif + DenseMatrix PointMat_bar(dimc, ndof); + DenseMatrix PointMat_bar_2(dimc, ndof); + Vector b_hat(dimc); - for (int i = 0; i < ir->GetNPoints(); i++) + // cast the ElementTransformation + auto &isotrans = dynamic_cast(*trans); + + for (int i = 0; i < ir->GetNPoints(); ++i) { const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - w = ip.weight; - - if ( dim == 3 ) + trans->SetIntPoint(&ip); + if (dim == 3) { el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + MultABt(curlshape, trans->Jacobian(), curlshape_dFt); } else { el.CalcCurlShape(ip, curlshape_dFt); } - b_vec = 0.0; curlshape_dFt.AddMultTranspose(elfun, b_vec); - b_vec /= trans.Weight(); - const double b_mag = b_vec.Norml2(); - const double nu_val = nu->Eval(trans, ip, b_mag); + const double b_mag = b_vec.Norml2() / isotrans.Weight(); + const double nu_val = nu->Eval(*trans, ip, b_mag); - const double qp_en = integrateBH(segment_ir, trans, ip, - 0.0, nu_val * b_mag); + // reverse pass - fun += qp_en * w; - } - return fun; -} + PointMat_bar = 0.0; + b_hat = 0.0; + curlshape.AddMultTranspose(elfun, b_hat); -void MagneticCoenergyIntegrator::AssembleElementVector( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun, - mfem::Vector &elvect) -{ - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); + const double nu_deriv = nu->EvalStateDeriv(*trans, ip, b_mag); - elvect.SetSize(ndof); - elvect = 0.0; + DenseMatrix dBdJ(b_hat.Size(), b_vec.Size()); + dBdJ = 0.0; + AddMult_a_VWt( + 1.0 / (b_vec.Norml2() * isotrans.Weight()), b_vec, b_hat, dBdJ); - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + isotrans.WeightRevDiff(PointMat_bar); + PointMat_bar *= -b_vec.Norml2() / pow(isotrans.Weight(), 2); - /// holds quadrature weight - double w; + isotrans.JacobianRevDiff(dBdJ, PointMat_bar); -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof,dimc), curlshape_dFt(ndof,dimc), M; - Vector b_vec(dimc), temp_vec(ndof); -#else - curlshape.SetSize(ndof,dimc); - curlshape_dFt.SetSize(ndof,dimc); - b_vec.SetSize(dimc); - temp_vec.SetSize(ndof); -#endif + PointMat_bar *= nu_deriv / isotrans.Weight(); - const IntegrationRule *ir = NULL; - const IntegrationRule *segment_ir = NULL; - { - int order; - if (el.Space() == FunctionSpace::Pk) - { - order = 2*el.GetOrder() - 2; - } - else + PointMat_bar_2 = 0.0; + isotrans.WeightRevDiff(PointMat_bar_2); + PointMat_bar_2 *= -nu_val / pow(isotrans.Weight(), 2); + + for (int j = 0; j < ndof; ++j) { - order = 2*el.GetOrder(); + for (int d = 0; d < dimc; ++d) + { + elvect(d * ndof + j) += + ip.weight * (PointMat_bar(d, j) + PointMat_bar_2(d, j)); + } } - - ir = &IntRules.Get(el.GetGeomType(), order); - } - /// TODO make segment's integration much higher than elements - { - // int order; - // if (el.Space() == FunctionSpace::Pk) - // { - // order = 2*el.GetOrder() - 2; - // } - // else - // { - // order = 2*el.GetOrder(); - // } - - // segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 2*(order+1)); - segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); } +} - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - - trans.SetIntPoint(&ip); +void ThermalSensIntegrator::AssembleRHSElementVect( + const FiniteElement &nd_el, + ElementTransformation &nd_trans, + Vector &elvect) +{ + /// get the proper element, transformation, and adjoint vector + int element = nd_trans.ElementNo; + const auto &el = *adjoint->FESpace()->GetFE(element); + auto &trans = *adjoint->FESpace()->GetElementTransformation(element); - w = ip.weight / trans.Weight(); + Array vdofs; + adjoint->FESpace()->GetElementVDofs(element, vdofs); + Vector psi; + adjoint->GetSubVector(vdofs, psi); - if ( dim == 3 ) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + const IntegrationRule *ir = + &IntRules.Get(el.GetGeomType(), oa * el.GetOrder() + ob); - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - b_vec /= trans.Weight(); - const double b_mag = b_vec.Norml2(); - const double nu_val = nu->Eval(trans, ip, b_mag); - const double dnu_dB = nu->EvalStateDeriv(trans, ip, b_mag); + int h1_dof = el.GetDof(); + shape.SetSize(h1_dof); + int nd_dof = nd_el.GetDof(); + elvect.SetSize(nd_dof); + elvect = 0.0; - /// temp_vec = curl(N_i) dot curl(A) - temp_vec = 0.0; - curlshape_dFt.Mult(b_vec, temp_vec); - double dwp_dh = RevADintegrateBH(segment_ir, trans, ip, - 0, nu_val * b_mag); - temp_vec *= dwp_dh*(dnu_dB + nu_val/b_mag); - temp_vec *= w; - elvect += temp_vec; - } -} + Vector V(nd_dof); -double MagneticCoenergyIntegrator::integrateBH( - const IntegrationRule *ir, - ElementTransformation &trans, - const IntegrationPoint &old_ip, - double lower_bound, - double upper_bound) -{ - /// compute int_0^{\nu*B} \frac{H}{\nu} dH - double qp_en = 0.0; - for (int j = 0; j < ir->GetNPoints(); j++) + for (int i = 0; i < ir->GetNPoints(); i++) { - const IntegrationPoint &ip = ir->IntPoint(j); - double xi = ip.x * (upper_bound - lower_bound); - qp_en += ip.weight * xi / nu->Eval(trans, old_ip, xi); + const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + el.CalcShape(ip, shape); + + double Q_bar = trans.Weight() * (psi * shape); // d(psi^T R)/dQ + Q.Eval(V, trans, ip); // evaluate dQ/dA + add(elvect, ip.weight * Q_bar, V, elvect); } - qp_en *= (upper_bound - lower_bound); - return qp_en; } -double MagneticCoenergyIntegrator::FDintegrateBH( - const IntegrationRule *ir, - ElementTransformation &trans, - const IntegrationPoint &old_ip, - double lower_bound, - double upper_bound) -{ - double delta = 1e-5; - - 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); -} +// void setInputs(DCLossFunctionalIntegrator &integ, const MachInputs +// &inputs) +// { +// setValueFromInputs(inputs, "rms_current", integ.rms_current); +// } -double MagneticCoenergyIntegrator::RevADintegrateBH( - const IntegrationRule *ir, - ElementTransformation &trans, - const IntegrationPoint &old_ip, - double lower_bound, - double upper_bound) +double DCLossFunctionalIntegrator::GetElementEnergy( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { - /// compute int_0^{\nu*B} \frac{H}{\nu} dH - double qp_en = 0.0; - for (int j = 0; j < ir->GetNPoints(); j++) + const IntegrationRule *ir = IntRule; + if (ir == nullptr) { - const IntegrationPoint &ip = ir->IntPoint(j); - double xi = ip.x * (upper_bound - lower_bound); - qp_en += ip.weight * xi / nu->Eval(trans, old_ip, xi); - } + int order = [&]() + { + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); - /// insert forward code here to compute qp_en, but not the last part - /// where you multiply by (upper_bound - lower_bound) - /// start reverse mode for int_0^{\nu*B} \frac{H}{\nu} dH - // return qp_en*(upper_bound - lower_bound); - double upper_bound_bar = qp_en; - double qp_en_bar = (upper_bound - lower_bound); - for (int j = 0; j < ir->GetNPoints(); j++) - { - const IntegrationPoint &ip = ir->IntPoint(j); - 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)); - // double xi = ip.x * (upper_bound - lower_bound); - upper_bound_bar += ip.x*xi_bar; + 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); + + double trans_weight = trans.Weight(); + + double w = ip.weight * trans_weight; + const double sigma_v = sigma.Eval(trans, ip); + fun += w / sigma_v; } - return upper_bound_bar; + return fun; } -void MagneticCoenergyIntegrator::AssembleRHSElementVect( - const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +void DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect( + const mfem::FiniteElement &mesh_el, + mfem::ElementTransformation &mesh_trans, + mfem::Vector &mesh_coords_bar) { - /// get the proper element, transformation, and state vector - Array vdofs; Vector elfun; - int element = mesh_trans.ElementNo; - const FiniteElement *el = state.FESpace()->GetFE(element); - ElementTransformation *trans = -state.FESpace()->GetElementTransformation(element); - state.FESpace()->GetElementVDofs(element, vdofs); + const int element = mesh_trans.ElementNo; + const auto &el = *state.FESpace()->GetFE(element); + auto &trans = *state.FESpace()->GetElementTransformation(element); - const IntegrationRule *ir = NULL; - const IntegrationRule *segment_ir = NULL; - { - int order; - if (el->Space() == FunctionSpace::Pk) - { - order = 2*el->GetOrder() - 2; - } - else - { - order = 2*el->GetOrder(); - } + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); - ir = &IntRules.Get(el->GetGeomType(), order); - } - /// TODO make segment's integration much higher than elements + 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) - { - order = 2*el->GetOrder() - 2; - } - else + int order = [&]() { - order = 2*el->GetOrder(); - } + if (el.Space() == FunctionSpace::Pk) + { + return 2 * el.GetOrder() - 2; + } + else + { + return 2 * el.GetOrder(); + } + }(); - segment_ir = &IntRules.Get(Geometry::Type::SEGMENT, 12); + ir = &IntRules.Get(el.GetGeomType(), order); } - state.GetSubVector(vdofs, elfun); - - int ndof = mesh_el.GetDof(); - int el_ndof = el->GetDof(); - int dim = el->GetDim(); - int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof*dimc); - elvect = 0.0; -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof,dimc), curlshape_dFt(ndof,dimc), M; - Vector b_vec(dimc), b_hat(dimc); -#else - curlshape.SetSize(el_ndof,dimc); - curlshape_dFt.SetSize(el_ndof,dimc); - b_vec.SetSize(dimc); - b_hat.SetSize(dimc); -#endif - DenseMatrix PointMat_bar(dimc, ndof); - // cast the ElementTransformation - IsoparametricTransformation &isotrans = - dynamic_cast(*trans); + 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++) { - PointMat_bar = 0.0; + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); - const IntegrationPoint &ip = ir->IntPoint(i); - trans->SetIntPoint(&ip); - // double w = ip.weight / trans->Weight(); - if ( dim == 3 ) - { - el->CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans->Jacobian(), curlshape_dFt); - } - else - { - el->CalcCurlShape(ip, curlshape_dFt); - } + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; - b_hat = 0.0; - b_vec = 0.0; - curlshape.AddMultTranspose(elfun, b_hat); - curlshape_dFt.AddMultTranspose(elfun, b_vec); - b_vec /= trans->Weight(); - const double b_mag = b_vec.Norml2(); + const double sigma_v = sigma.Eval(trans, ip); + // fun += w / sigma_v; - const double nu_val = nu->Eval(*trans, ip, b_mag); - const double dnu_dB = nu->EvalStateDeriv(*trans, ip, b_mag); + /// Start reverse pass... + double fun_bar = 1.0; - const double wp = integrateBH(segment_ir, *trans, ip, - 0.0, nu_val * b_mag); + /// fun += w / sigma_v; + double w_bar = fun_bar / sigma_v; + double sigma_v_bar = -fun_bar * w / pow(sigma_v, 2); - // start reverse sweep - const double dwp_dh = RevADintegrateBH(segment_ir, *trans, ip, - 0.0, nu_val * b_mag); + // std::cout << "sigma_v_bar: " << sigma_v_bar << "\n"; + // std::cout << "fun_bar: " << fun_bar << " w: " << w << " sigma_v: " << + // sigma_v << "\n"; - DenseMatrix BB_hatT(3); - MultVWt(b_vec, b_hat, BB_hatT); - BB_hatT *= dwp_dh*(dnu_dB + nu_val/b_mag); // / trans->Weight(); + /// const double sigma_v = sigma.Eval(trans, ip); + PointMat_bar = 0.0; + sigma.EvalRevDiff(sigma_v_bar, trans, ip, PointMat_bar); - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= -wp / pow(trans->Weight(), 2.0); - isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); + /// double w = ip.weight * trans_weight; + double trans_weight_bar = w_bar * ip.weight; - for (int j = 0; j < ndof ; ++j) + /// 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 < dimc; ++d) + for (int d = 0; d < space_dim; ++d) { - elvect(d*ndof + j) += ip.weight * PointMat_bar(d,j); + mesh_coords_bar(d * mesh_ndof + j) += PointMat_bar(d, j); } } } } -*/ -double BNormIntegrator::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +void setInputs(DCLossFunctionalDistributionIntegrator &integ, + const MachInputs &inputs) { - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); + setValueFromInputs(inputs, "wire_length", integ.wire_length); + setValueFromInputs(inputs, "rms_current", integ.rms_current); + setValueFromInputs(inputs, "strand_radius", integ.strand_radius); + setValueFromInputs(inputs, "strands_in_hand", integ.strands_in_hand); +} - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; +void DCLossFunctionalDistributionIntegrator::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) +{ + int ndof = el.GetDof(); #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); + Vector shape; #endif + shape.SetSize(ndof); + elvect.SetSize(ndof); - const IntegrationRule *ir = IntRule; + const auto *ir = IntRule; if (ir == nullptr) { int order = [&]() { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 2; + return 2 * el.GetOrder() - 1; } else { @@ -1913,59 +3594,42 @@ double BNormIntegrator::GetElementEnergy(const FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } - double fun = 0.0; - + elvect = 0.0; for (int i = 0; i < ir->GetNPoints(); i++) { - b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); + el.CalcPhysShape(trans, shape); + /// holds quadrature weight - auto 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 - { - el.CalcCurlShape(ip, curlshape_dFt); - } + const auto sigma_v = sigma.Eval(trans, ip); - curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_mag = b_vec.Norml2() / trans.Weight(); - fun += b_mag * w; + double strand_area = M_PI * pow(strand_radius, 2); + double R = wire_length / (strand_area * strands_in_hand * sigma_v); + + double loss = pow(rms_current, 2) * R; + // not sure about this... but it matches MotorCAD's values + loss *= sqrt(2); + + elvect.Add(loss * w, shape); } - return fun; } -void BNormIntegrator::AssembleElementVector(const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun, - mfem::Vector &elvect) +double ACLossFunctionalIntegrator::GetElementEnergy( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { /// number of degrees of freedom int ndof = el.GetDof(); - int dim = el.GetDim(); - - elvect.SetSize(ndof); - elvect = 0.0; - - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc), temp_vec(ndof); + Vector shape(ndof); #else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - b_vec.SetSize(dimc); - temp_vec.SetSize(ndof); + shape.SetSize(ndof); #endif const IntegrationRule *ir = IntRule; @@ -1986,59 +3650,126 @@ void BNormIntegrator::AssembleElementVector(const mfem::FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } + 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); /// holds quadrature weight - auto w = ip.weight / trans.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.CalcCurlShape(ip, curlshape_dFt); - } + el.CalcPhysShape(trans, shape); + const auto b_mag = shape * elfun; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - double b_mag = b_vec.Norml2() / trans.Weight(); + const auto sigma_val = sigma.Eval(trans, ip); - /// temp_vec = curl(N_i) dot curl(A) - temp_vec = 0.0; - curlshape_dFt.Mult(b_vec, temp_vec); - temp_vec /= b_mag; - temp_vec *= w; - elvect += temp_vec; + const auto loss = sigma_val * pow(b_mag, 2); + fun += loss * w; } + return fun; } -double BNormSquaredIntegrator::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +// 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) { - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); + const int element = mesh_trans.ElementNo; + const auto &el = *peak_flux.FESpace()->GetFE(element); + auto &trans = *peak_flux.FESpace()->GetElementTransformation(element); - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + const int ndof = el.GetDof(); + const int mesh_ndof = mesh_el.GetDof(); + const int space_dim = mesh_trans.GetSpaceDim(); + + 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 - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); + mfem::Vector shape; + mfem::Vector shape_bar; #else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + auto &shape = integ.shape; #endif - double b_vec_buffer[3]; - Vector b_vec(b_vec_buffer, dimc); + 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) @@ -2058,43 +3789,90 @@ double BNormSquaredIntegrator::GetElementEnergy(const FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } - double fun = 0.0; + 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 IntegrationPoint &ip = ir->IntPoint(i); + const auto &ip = ir->IntPoint(i); trans.SetIntPoint(&ip); - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + double trans_weight = trans.Weight(); + double w = ip.weight * trans_weight; - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); + el.CalcPhysShape(trans, shape); + const double b_mag = shape * elfun; - auto trans_weight = trans.Weight(); - const double b_mag = b_vec.Norml2() / trans_weight; - fun += b_mag * b_mag * ip.weight * trans_weight; + 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); + } + } } - return fun; } -void BNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +void ACLossFunctionalIntegratorPeakFluxSens::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elfun_bar) { - /// get the proper element, transformation, and state vector - Array vdofs; - Vector elfun; - int element = mesh_trans.ElementNo; - const FiniteElement &el = *state.FESpace()->GetFE(element); - ElementTransformation *trans = - state.FESpace()->GetElementTransformation(element); + 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) @@ -2114,116 +3892,92 @@ void BNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, ir = &IntRules.Get(el.GetGeomType(), order); } - auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); - state.GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } - - int ndof = mesh_el.GetDof(); - int el_ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof * dimc); - elvect = 0.0; -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); -#else - curlshape.SetSize(el_ndof, dimc); - curlshape_dFt.SetSize(el_ndof, dimc); - b_vec.SetSize(dimc); -#endif - DenseMatrix PointMat_bar(dimc, ndof); - Vector b_hat(dimc); - - // cast the ElementTransformation - auto &isotrans = dynamic_cast(*trans); + 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); - double w = ip.weight; - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans->Jacobian(), curlshape_dFt); - } - else - { - el.CalcCurlShape(ip, curlshape_dFt); - } + trans.SetIntPoint(&ip); - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - // start reverse sweep - PointMat_bar = 0.0; + /// holds quadrature weight + double trans_weight = trans.Weight(); + const double w = ip.weight * trans_weight; - /** the following computes `\partial (||B||/|J|) / \partial X` - * This is useful elsewhere, but for the test to pass we need - * `\partial (||B||/|J|)*(w*|J|) / \partial X`... + el.CalcPhysShape(trans, shape); + const auto b_mag = shape * elfun; - double weight_bar = -b_vec.Norml2() / pow(trans->Weight(), 2.0); - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= weight_bar; + const auto sigma_v = sigma.Eval(trans, ip); - b_hat = 0.0; - curlshape.AddMultTranspose(elfun, b_hat); - DenseMatrix BB_hatT(3); - MultVWt(b_vec, b_hat, BB_hatT); - BB_hatT *= 1.0 / (trans->Weight() * b_vec.Norml2()); - isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); - */ + // const auto loss = sigma_v * pow(b_mag, 2); + // fun += loss * w; - b_hat = 0.0; - curlshape.AddMultTranspose(elfun, b_hat); - DenseMatrix BB_hatT(3); - MultVWt(b_vec, b_hat, BB_hatT); - BB_hatT *= 1.0 / b_vec.Norml2(); - isotrans.JacobianRevDiff(BB_hatT, PointMat_bar); + /// Start reverse pass... + double fun_bar = 1.0; - // code to insert PointMat_bar into elvect; - for (int j = 0; j < ndof; ++j) - { - for (int d = 0; d < dimc; ++d) - { - elvect(d * ndof + j) += w * PointMat_bar(d, j); - } - } + /// 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); } } -double nuBNormIntegrator::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +void setInputs(ACLossFunctionalDistributionIntegrator &integ, + const MachInputs &inputs) +{ + setValueFromInputs(inputs, "frequency", integ.freq); + setValueFromInputs(inputs, "strand_radius", integ.radius); + setValueFromInputs(inputs, "stack_length", integ.stack_length); + setValueFromInputs(inputs, "strands_in_hand", integ.strands_in_hand); + setValueFromInputs(inputs, "num_turns", integ.num_turns); + setValueFromInputs(inputs, "num_slots", integ.num_slots); +} + +void ACLossFunctionalDistributionIntegrator::AssembleRHSElementVect( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + mfem::Vector &elvect) { - /// 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; + const int element = trans.ElementNo; + const auto &flux_el = *peak_flux.FESpace()->GetFE(element); + auto &flux_trans = *peak_flux.FESpace()->GetElementTransformation(element); + const int flux_ndof = flux_el.GetDof(); #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); + mfem::Array vdofs; + mfem::Vector elfun; #endif - const IntegrationRule *ir = IntRule; + 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; + mfem::Vector flux_shape; +#endif + shape.SetSize(ndof); + flux_shape.SetSize(flux_ndof); + elvect.SetSize(ndof); + + const auto *ir = IntRule; if (ir == nullptr) { int order = [&]() { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 2; + return 2 * el.GetOrder() - 1; } else { @@ -2234,59 +3988,73 @@ double nuBNormIntegrator::GetElementEnergy(const FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } - double fun = 0.0; - + elvect = 0.0; for (int i = 0; i < ir->GetNPoints(); i++) { - b_vec = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); + el.CalcPhysShape(trans, shape); + + flux_el.CalcPhysShape(flux_trans, flux_shape); + const auto b_mag = flux_shape * elfun; + /// holds quadrature weight - auto 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 - { - el.CalcCurlShape(ip, curlshape_dFt); - } + const auto sigma_v = sigma.Eval(trans, ip); - curlshape_dFt.AddMultTranspose(elfun, b_vec); - fun += nu->Eval(trans, ip, b_vec.Norml2()) * b_vec.Norml2() * w; + double loss = stack_length * M_PI * pow(radius, 4) * + pow(2 * M_PI * freq * b_mag, 2) * sigma_v / 32.0; + loss *= 2 * strands_in_hand * num_turns * num_slots; + + elvect.Add(loss * w, shape); } - return fun; } -void nuBNormIntegrator::AssembleElementVector( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun, - mfem::Vector &elvect) +void setInputs(HybridACLossFunctionalIntegrator &integ, + const MachInputs &inputs) +{ + // auto it = inputs.find("diam"); + // if (it != inputs.end()) + // { + // integ.diam = it->second.getValue(); + // } + // it = inputs.find("diam"); + // if (it != inputs.end()) + // { + // integ.freq = it->second.getValue(); + // } + // it = inputs.find("fill-factor"); + // if (it != inputs.end()) + // { + // integ.fill_factor = it->second.getValue(); + // } + setValueFromInputs(inputs, "diam", integ.diam); + setValueFromInputs(inputs, "freq", integ.freq); + setValueFromInputs(inputs, "fill-factor", integ.fill_factor); +} + +double HybridACLossFunctionalIntegrator::GetElementEnergy( + const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { /// number of degrees of freedom int ndof = el.GetDof(); int dim = el.GetDim(); - elvect.SetSize(ndof); - elvect = 0.0; - /// I believe this takes advantage of a 2D problem not having /// a properly defined curl? Need more investigation int dimc = (dim == 3) ? 3 : 1; #ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc), temp_vec(ndof); + DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); + Vector b_vec(dimc); #else curlshape.SetSize(ndof, dimc); curlshape_dFt.SetSize(ndof, dimc); b_vec.SetSize(dimc); - temp_vec.SetSize(ndof); #endif const IntegrationRule *ir = IntRule; @@ -2307,6 +4075,8 @@ void nuBNormIntegrator::AssembleElementVector( ir = &IntRules.Get(el.GetGeomType(), order); } + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) { b_vec = 0.0; @@ -2314,8 +4084,7 @@ void nuBNormIntegrator::AssembleElementVector( trans.SetIntPoint(&ip); - auto w = ip.weight / trans.Weight(); - + auto w = ip.weight * trans.Weight(); if (dim == 3) { el.CalcCurlShape(ip, curlshape); @@ -2327,30 +4096,67 @@ void nuBNormIntegrator::AssembleElementVector( } curlshape_dFt.AddMultTranspose(elfun, b_vec); - double b_mag = b_vec.Norml2(); + const double b_mag = b_vec.Norml2() / trans.Weight(); - /// temp_vec = curl(N_i) dot curl(A) - temp_vec = 0.0; - curlshape_dFt.Mult(b_vec, temp_vec); - double nu_val = nu->Eval(trans, ip, b_mag); - double dnu_dB = nu->EvalStateDeriv(trans, ip, b_mag); - temp_vec *= (dnu_dB + nu_val / b_mag); - temp_vec *= w; - elvect += temp_vec; + const double sigma_val = sigma.Eval(trans, ip); + + const double loss = std::pow(diam, 2) * sigma_val * + std::pow(2 * M_PI * freq * b_mag, 2) / 128.0; + fun += loss * fill_factor * w; } + return fun; } -void nuBNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) +double ForceIntegrator3::GetElementEnergy(const FiniteElement &el, + ElementTransformation &trans, + const Vector &elfun) { - /// get the proper element, transformation, and state vector + if (attrs.count(trans.Attribute) == 1) + { + return 0.0; + } + /// get the proper element, transformation, and v vector +#ifdef MFEM_THREAD_SAFE Array vdofs; - Vector elfun; - int element = mesh_trans.ElementNo; - const FiniteElement &el = *state.FESpace()->GetFE(element); - ElementTransformation *trans = - state.FESpace()->GetElementTransformation(element); + 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; + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + 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()); + // 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) @@ -2370,147 +4176,151 @@ void nuBNormdJdx::AssembleRHSElementVect(const FiniteElement &mesh_el, ir = &IntRules.Get(el.GetGeomType(), order); } - auto *dof_tr = state.FESpace()->GetElementVDofs(element, vdofs); - state.GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } - - int ndof = mesh_el.GetDof(); - int el_ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof * dimc); - elvect = 0.0; -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); -#else - curlshape.SetSize(el_ndof, dimc); - curlshape_dFt.SetSize(el_ndof, dimc); - b_vec.SetSize(dimc); -#endif - // DenseMatrix PointMat_bar(dimc, ndof); - DenseMatrix PointMat_bar_1(dimc, ndof); - DenseMatrix PointMat_bar_2(dimc, ndof); - DenseMatrix PointMat_bar_3(dimc, ndof); - // Vector DofVal(elfun.Size()); - - Vector b_hat(dimc); - - // cast the ElementTransformation - auto &isotrans = dynamic_cast(*trans); - - for (int i = 0; i < ir->GetNPoints(); ++i) + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) { - PointMat_bar_1 = 0.0; - PointMat_bar_2 = 0.0; - PointMat_bar_3 = 0.0; - b_vec = 0.0; - b_hat = 0.0; const IntegrationPoint &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + + const double trans_weight = trans.Weight(); + /// holds quadrature weight + double w = ip.weight * trans_weight; - trans->SetIntPoint(&ip); if (dim == 3) { el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans->Jacobian(), curlshape_dFt); + MultABt(curlshape, trans.Jacobian(), curlshape_dFt); } - else + else // Dealing with scalar H1 field representing Az { - el.CalcCurlShape(ip, curlshape_dFt); + /// 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); } - curlshape.AddMultTranspose(elfun, b_hat); - curlshape_dFt.AddMultTranspose(elfun, b_vec); - double nu_val = nu->Eval(*trans, ip, b_vec.Norml2()); - double nu_deriv = nu->EvalStateDeriv(*trans, ip, b_vec.Norml2()); + // 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; - Vector dNormBdB(b_vec); - dNormBdB /= b_vec.Norml2(); - DenseMatrix dBdJ(b_hat.Size(), b_vec.Size()); - MultVWt(dNormBdB, b_hat, dBdJ); - isotrans.JacobianRevDiff(dBdJ, PointMat_bar_1); - PointMat_bar_1 *= nu_val / isotrans.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; - isotrans.WeightRevDiff(PointMat_bar_2); - PointMat_bar_2 *= -nu_val * b_vec.Norml2() / pow(isotrans.Weight(), 2); + 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); - isotrans.JacobianRevDiff(dBdJ, PointMat_bar_3); - PointMat_bar_3 *= b_vec.Norml2() * nu_deriv / isotrans.Weight(); - // 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(); - // if ( dim == 3 ) - // { - // el->CalcCurlShape(ip, curlshape); - // MultABt(curlshape, trans->Jacobian(), curlshape_dFt); - // } - // else - // { - // el->CalcCurlShape(ip, curlshape_dFt); - // } - // curlshape_dFt.AddMultTranspose(elfun, b_vec); - // // start reverse sweep + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); - // PointMat_bar = 0.0; - // // fun += b_vec.Norml2() * w; - // Vector b_vec_bar(b_vec); - // b_vec_bar *= w / b_vec.Norml2(); - // double w_bar = b_vec.Norml2(); - // // curlshape_dFt.AddMultTranspose(elfun, b_vec); - // DenseMatrix curlshape_dFt_bar(elfun.Size(), b_vec_bar.Size()); - // MultVWt(elfun, b_vec_bar, curlshape_dFt_bar); - // // MultABt(curlshape, trans.Jacobian(), curlshape_dFt); - // DenseMatrix Jac_bar(3); - // MultAtB(curlshape_dFt_bar, curlshape, Jac_bar); - // // w = ip.weight / trans.Weight(); - // double weight_bar = -w_bar*ip.weight/pow(trans->Weight(), 2.0); - // 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); - // // code to insert PointMat_bar into elvect; + 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); - for (int j = 0; j < ndof; ++j) + 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 d = 0; d < dimc; ++d) + for (int k = 0; k < space_dim; ++k) { - elvect(d * ndof + j) += - ip.weight * (PointMat_bar_1(d, j) + PointMat_bar_2(d, j) + - PointMat_bar_3(d, j)); - // elvect(d*ndof + j) += PointMat_bar(d,j); + 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; } + return fun; } -double nuFuncIntegrator::GetElementEnergy(const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +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; - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + 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 - 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); + 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()); - const IntegrationRule *ir = mfem::NonlinearFormIntegrator::IntRule; + 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 = [&]() @@ -2528,304 +4338,323 @@ double nuFuncIntegrator::GetElementEnergy(const FiniteElement &el, ir = &IntRules.Get(el.GetGeomType(), order); } - 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); - auto w = ip.weight / trans.Weight(); + 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 + else // Dealing with scalar H1 field representing Az { - el.CalcCurlShape(ip, curlshape_dFt); + /// 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_mag = b_vec.Norml2() / trans.Weight(); - const double nu_val = nu->Eval(trans, ip, b_mag); - fun += nu_val * w; - } - return fun; -} - -void nuFuncIntegrator::AssembleRHSElementVect(const FiniteElement &mesh_el, - ElementTransformation &mesh_trans, - Vector &elvect) -{ - /// get the proper element, transformation, and state vector - Array vdofs; - Vector elfun; - int element = mesh_trans.ElementNo; - const FiniteElement &el = *state->FESpace()->GetFE(element); - ElementTransformation *trans = - state->FESpace()->GetElementTransformation(element); + const double b_vec_norm = b_vec.Norml2(); + const double b_mag = b_vec_norm / trans_weight; - const IntegrationRule *ir = mfem::NonlinearFormIntegrator::IntRule; - if (ir == nullptr) - { - int order = [&]() + /// 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) { - if (el.Space() == FunctionSpace::Pk) - { - return 2 * el.GetOrder() - 2; - } - else - { - return 2 * el.GetOrder(); - } - }(); - - ir = &IntRules.Get(el.GetGeomType(), order); - } - - auto *dof_tr = state->FESpace()->GetElementVDofs(element, vdofs); - state->GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } - - int ndof = mesh_el.GetDof(); - int el_ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; - elvect.SetSize(ndof * dimc); - elvect = 0.0; -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc), M; - Vector b_vec(dimc); -#else - curlshape.SetSize(el_ndof, dimc); - curlshape_dFt.SetSize(el_ndof, dimc); - b_vec.SetSize(dimc); -#endif - DenseMatrix PointMat_bar(dimc, ndof); - DenseMatrix PointMat_bar_2(dimc, ndof); - Vector b_hat(dimc); + double b_hat_buffer[3] = {}; + Vector b_hat(b_hat_buffer, curl_dim); + b_hat = 0.0; - // cast the ElementTransformation - auto &isotrans = dynamic_cast(*trans); + 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); - for (int i = 0; i < ir->GetNPoints(); ++i) - { - const IntegrationPoint &ip = ir->IntPoint(i); + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); - trans->SetIntPoint(&ip); - if (dim == 3) - { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans->Jacobian(), curlshape_dFt); + db_magdJ.Add(1.0 / (trans_weight * b_vec_norm), BB_hatT); } else { - el.CalcCurlShape(ip, curlshape_dFt); - } - b_vec = 0.0; - curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_mag = b_vec.Norml2() / isotrans.Weight(); - const double nu_val = nu->Eval(*trans, ip, b_mag); - - // reverse pass - - PointMat_bar = 0.0; - b_hat = 0.0; - curlshape.AddMultTranspose(elfun, b_hat); - - const double nu_deriv = nu->EvalStateDeriv(*trans, ip, b_mag); - - DenseMatrix dBdJ(b_hat.Size(), b_vec.Size()); - dBdJ = 0.0; - AddMult_a_VWt( - 1.0 / (b_vec.Norml2() * isotrans.Weight()), b_vec, b_hat, dBdJ); + double b_adjJT_buffer[3] = {}; + Vector b_adjJT(b_adjJT_buffer, curl_dim); + trans.AdjugateJacobian().Mult(b_vec, b_adjJT); - isotrans.WeightRevDiff(PointMat_bar); - PointMat_bar *= -b_vec.Norml2() / pow(isotrans.Weight(), 2); - - isotrans.JacobianRevDiff(dBdJ, PointMat_bar); + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); - PointMat_bar *= nu_deriv / isotrans.Weight(); + AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + } - PointMat_bar_2 = 0.0; - isotrans.WeightRevDiff(PointMat_bar_2); - PointMat_bar_2 *= -nu_val / pow(isotrans.Weight(), 2); + /// contract d(b_mag)/dJ with dJ/dX + dBdX = 0.0; + isotrans.JacobianRevDiff(db_magdJ, dBdX); - for (int j = 0; j < ndof; ++j) + double dBds = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) { - for (int d = 0; d < dimc; ++d) + for (int k = 0; k < space_dim; ++k) { - elvect(d * ndof + j) += - ip.weight * (PointMat_bar(d, j) + PointMat_bar_2(d, j)); + dBds += dBdX(k, j) * dXds(j, k); } } - } -} + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + // auto force = dBds * energy_dot; -void ThermalSensIntegrator::AssembleRHSElementVect( - const FiniteElement &nd_el, - ElementTransformation &nd_trans, - Vector &elvect) -{ - /// get the proper element, transformation, and adjoint vector - int element = nd_trans.ElementNo; - const auto &el = *adjoint->FESpace()->GetFE(element); - auto &trans = *adjoint->FESpace()->GetElementTransformation(element); + 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(); - Array vdofs; - adjoint->FESpace()->GetElementVDofs(element, vdofs); - Vector psi; - adjoint->GetSubVector(vdofs, psi); + // const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + // double force2 = energy * JinvdJdsTrace; + // fun -= (force + force2) * w; - const IntegrationRule *ir = - &IntRules.Get(el.GetGeomType(), oa * el.GetOrder() + ob); + /// start reverse pass + double fun_bar = 1.0; - int h1_dof = el.GetDof(); - shape.SetSize(h1_dof); - int nd_dof = nd_el.GetDof(); - elvect.SetSize(nd_dof); - elvect = 0.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; - Vector V(nd_dof); + /// double force2 = energy * JinvdJdsTrace; + double energy_bar = force2_bar * JinvdJdsTrace; - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - el.CalcShape(ip, shape); + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + b_mag_bar += energy_bar * energy_dot; - double Q_bar = trans.Weight() * (psi * shape); // d(psi^T R)/dQ - Q.Eval(V, trans, ip); // evaluate dQ/dA - add(elvect, ip.weight * Q_bar, V, elvect); - } -} + /// auto force = dBds * energy_dot; + double dBds_bar = force_bar * energy_dot; + double energy_dot_bar = force_bar * dBds; -// void setInputs(DCLossFunctionalIntegrator &integ, const MachInputs &inputs) -// { -// setValueFromInputs(inputs, "rms_current", integ.rms_current); -// } + /// 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; -double DCLossFunctionalIntegrator::GetElementEnergy( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) -{ - const IntegrationRule *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() + 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) { - if (el.Space() == FunctionSpace::Pk) - { - return 2 * el.GetOrder() - 2; - } - else + for (int k = 0; k < space_dim; ++k) { - return 2 * el.GetOrder(); + /// dBds += dBdX(k, j) * dXds(j, k); + dBdX_bar(k, j) += dBds_bar * dXds(j, k); } - }(); - - 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); + /// 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 w = ip.weight * trans.Weight(); - const double sigma_v = sigma.Eval(trans, ip); - fun += w / sigma_v; - } - return fun; -} + 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); -void setInputs(DCLossFunctionalDistributionIntegrator &integ, - const MachInputs &inputs) -{ - setValueFromInputs(inputs, "wire_length", integ.wire_length); - setValueFromInputs(inputs, "rms_current", integ.rms_current); - setValueFromInputs(inputs, "strand_radius", integ.strand_radius); - setValueFromInputs(inputs, "strands_in_hand", integ.strands_in_hand); -} + double BB_hatT_buffer[9] = {}; + DenseMatrix BB_hatT(BB_hatT_buffer, curl_dim, curl_dim); + MultVWt(b_vec, b_hat, BB_hatT); -void DCLossFunctionalDistributionIntegrator::AssembleRHSElementVect( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - mfem::Vector &elvect) -{ - int ndof = el.GetDof(); + double BB_hatT_bar_buffer[9] = {}; + DenseMatrix BB_hatT_bar(BB_hatT_bar_buffer, curl_dim, curl_dim); + BB_hatT_bar = 0.0; -#ifdef MFEM_THREAD_SAFE - Vector shape; -#endif - shape.SetSize(ndof); - elvect.SetSize(ndof); + /// 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); - const auto *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() - { - if (el.Space() == FunctionSpace::Pk) + for (int j = 0; j < curl_dim; ++j) { - return 2 * el.GetOrder() - 1; + 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)); + } } - else + + /// 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) { - return 2 * el.GetOrder(); + 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); + } } - }(); - ir = &IntRules.Get(el.GetGeomType(), order); - } + double b_hat_bar_buffer[3] = {}; + Vector b_hat_bar(b_hat_bar_buffer, curl_dim); - elvect = 0.0; - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); + /// 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); - el.CalcPhysShape(trans, shape); + /// 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; - /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + /// 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)); - const auto sigma_v = sigma.Eval(trans, ip); + /// trans.AdjugateJacobian().Mult(b_vec, b_adjJT); + trans.AdjugateJacobian().AddMultTranspose(b_adjJT_bar, b_vec_bar); + } - double strand_area = M_PI * pow(strand_radius, 2); - double R = wire_length / (strand_area * strands_in_hand * sigma_v); + /// 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 loss = pow(rms_current, 2) * R; - // not sure about this... but it matches MotorCAD's values - loss *= sqrt(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); - elvect.Add(loss * w, shape); + /// curlshape_dFt.MultTranspose(elfun, b_vec); + curlshape_dFt.AddMult(b_vec_bar, elfun_bar); } } -double ACLossFunctionalIntegrator::GetElementEnergy( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) +void ForceIntegratorMeshSens3::AssembleRHSElementVect( + const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) { - /// number of degrees of freedom - int ndof = el.GetDof(); + 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 - Vector shape(ndof); + Array vdofs; + Vector vfun; + Vector elfun; #else - shape.SetSize(ndof); + 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); + 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) @@ -2845,204 +4674,320 @@ double ACLossFunctionalIntegrator::GetElementEnergy( ir = &IntRules.Get(el.GetGeomType(), order); } - double fun = 0.0; + 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(); + const double w = ip.weight * trans_weight; - el.CalcPhysShape(trans, shape); - const auto b_mag = shape * elfun; + 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); + } - const auto sigma_val = sigma.Eval(trans, ip); + 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 auto loss = sigma_val * pow(b_mag, 2); - fun += loss * w; - } - return fun; -} + /// 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; -void setInputs(ACLossFunctionalDistributionIntegrator &integ, - const MachInputs &inputs) -{ - setValueFromInputs(inputs, "frequency", integ.freq); - setValueFromInputs(inputs, "strand_radius", integ.radius); - setValueFromInputs(inputs, "stack_length", integ.stack_length); - setValueFromInputs(inputs, "strands_in_hand", integ.strands_in_hand); - setValueFromInputs(inputs, "num_turns", integ.num_turns); - setValueFromInputs(inputs, "num_slots", integ.num_slots); -} + 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); -void ACLossFunctionalDistributionIntegrator::AssembleRHSElementVect( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - mfem::Vector &elvect) -{ - int ndof = el.GetDof(); + db_magdJ.Add(-b_vec_norm / pow(trans_weight, 2), + trans.AdjugateJacobian()); + db_magdJ.Transpose(); - const int element = trans.ElementNo; - const auto &flux_el = *peak_flux.FESpace()->GetFE(element); - auto &flux_trans = *peak_flux.FESpace()->GetElementTransformation(element); - const int flux_ndof = flux_el.GetDof(); + 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); -#ifdef MFEM_THREAD_SAFE - mfem::Array vdofs; - mfem::Vector elfun; -#endif + double a = -1 / (b_vec_norm * pow(trans_weight, 2)); - auto *dof_tr = peak_flux.FESpace()->GetElementVDofs(element, vdofs); - peak_flux.GetSubVector(vdofs, elfun); - if (dof_tr != nullptr) - { - dof_tr->InvTransformPrimal(elfun); - } + AddMult_a_VWt(a, b_vec, b_adjJT, db_magdJ); + } -#ifdef MFEM_THREAD_SAFE - mfem::Vector shape; - mfem::Vector flux_shape; -#endif - shape.SetSize(ndof); - flux_shape.SetSize(flux_ndof); - elvect.SetSize(ndof); + /// contract d(b_mag)/dJ with dJ/dX + dBdX = 0.0; + isotrans.JacobianRevDiff(db_magdJ, dBdX); - const auto *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() + double dBds = 0.0; + for (int j = 0; j < v_el.GetDof(); ++j) { - if (el.Space() == FunctionSpace::Pk) - { - return 2 * el.GetOrder() - 1; - } - else + for (int k = 0; k < space_dim; ++k) { - return 2 * el.GetOrder(); + dBds += dBdX(k, j) * dXds(j, k); } - }(); + } + const double energy_dot = calcMagneticEnergyDot(trans, ip, nu, b_mag); + auto force = dBds * energy_dot; - ir = &IntRules.Get(el.GetGeomType(), order); - } + 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(); - elvect = 0.0; - for (int i = 0; i < ir->GetNPoints(); i++) - { - const IntegrationPoint &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); + const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double force2 = energy * JinvdJdsTrace; + // fun -= (force + force2) * w; - el.CalcPhysShape(trans, shape); + /// start reverse pass + double fun_bar = 1.0; - flux_el.CalcPhysShape(flux_trans, flux_shape); - const auto b_mag = flux_shape * elfun; + /// 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); - /// holds quadrature weight - const double w = ip.weight * trans.Weight(); + /// double force2 = energy * JinvdJdsTrace; + double energy_bar = force2_bar * JinvdJdsTrace; + double JinvdJdsTrace_bar = force2_bar * energy; - const auto sigma_v = sigma.Eval(trans, ip); + /// const double energy = calcMagneticEnergy(trans, ip, nu, b_mag); + double b_mag_bar = 0.0; + b_mag_bar += energy_bar * energy_dot; - double loss = stack_length * M_PI * pow(radius, 4) * - pow(2 * M_PI * freq * b_mag, 2) * sigma_v / 32.0; - loss *= 2 * strands_in_hand * num_turns * num_slots; + /// 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); - elvect.Add(loss * w, shape); - } -} + /// 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] = {}; + DenseMatrix scratch(scratch_buffer, space_dim, space_dim); + 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); + AddMult_a_ABt(-1.0, scratch, trans.InverseJacobian(), jac_bar); -void setInputs(HybridACLossFunctionalIntegrator &integ, - const MachInputs &inputs) -{ - // auto it = inputs.find("diam"); - // if (it != inputs.end()) - // { - // integ.diam = it->second.getValue(); - // } - // it = inputs.find("diam"); - // if (it != inputs.end()) - // { - // integ.freq = it->second.getValue(); - // } - // it = inputs.find("fill-factor"); - // if (it != inputs.end()) - // { - // integ.fill_factor = it->second.getValue(); - // } - setValueFromInputs(inputs, "diam", integ.diam); - setValueFromInputs(inputs, "freq", integ.freq); - setValueFromInputs(inputs, "fill-factor", integ.fill_factor); -} + /// MultAtB(dXds, dshape, dJds); // does not depend on mesh nodes + /// v_el.CalcDShape(ip, dshape); // does not depend on mesh nodes -double HybridACLossFunctionalIntegrator::GetElementEnergy( - const FiniteElement &el, - ElementTransformation &trans, - const Vector &elfun) -{ - /// number of degrees of freedom - int ndof = el.GetDof(); - int dim = el.GetDim(); + /// 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; - /// I believe this takes advantage of a 2D problem not having - /// a properly defined curl? Need more investigation - int dimc = (dim == 3) ? 3 : 1; + /// 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); + } + } -#ifdef MFEM_THREAD_SAFE - DenseMatrix curlshape(ndof, dimc), curlshape_dFt(ndof, dimc); - Vector b_vec(dimc); -#else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); - b_vec.SetSize(dimc); -#endif + /// 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); - const IntegrationRule *ir = IntRule; - if (ir == nullptr) - { - int order = [&]() + // /// 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] = {}; + DenseMatrix adj_jac_bar(adj_jac_bar_buffer, space_dim, space_dim); + if (dim == 3) { - if (el.Space() == FunctionSpace::Pk) + 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) { - return 2 * el.GetOrder() - 2; + 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)); + } } - else + + /// 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) { - return 2 * el.GetOrder(); + 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); + } } - }(); - ir = &IntRules.Get(el.GetGeomType(), order); - } + // double b_hat_bar_buffer[3] = {}; + // Vector b_hat_bar(b_hat_bar_buffer, curl_dim); - double fun = 0.0; + /// 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 - for (int i = 0; i < ir->GetNPoints(); i++) - { - b_vec = 0.0; - const IntegrationPoint &ip = ir->IntPoint(i); + /// 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; - trans.SetIntPoint(&ip); + /// 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); - auto w = ip.weight * trans.Weight(); if (dim == 3) { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + /// 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 + else // Dealing with scalar H1 field representing Az { - el.CalcCurlShape(ip, curlshape_dFt); + /// 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; } - curlshape_dFt.AddMultTranspose(elfun, b_vec); - const double b_mag = b_vec.Norml2() / trans.Weight(); + PointMat_bar = 0.0; + isotrans.AdjugateJacobianRevDiff(adj_jac_bar, PointMat_bar); - const double sigma_val = sigma.Eval(trans, ip); + /// const double w = ip.weight * trans.Weight(); + trans_weight_bar += w_bar * ip.weight; - const double loss = std::pow(diam, 2) * sigma_val * - std::pow(2 * M_PI * freq * b_mag, 2) / 128.0; - fun += loss * fill_factor * w; + 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); + } + } } - return fun; } double ForceIntegrator::GetElementEnergy(const FiniteElement &el, @@ -3070,23 +5015,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 = 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 @@ -3116,39 +5059,54 @@ 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(); + 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); + /// 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 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); - 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; @@ -3157,7 +5115,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); } @@ -3166,8 +5124,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(); @@ -3332,7 +5292,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 +5551,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 +5628,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); @@ -3696,7 +5658,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); @@ -3719,7 +5681,7 @@ double SteinmetzLossIntegrator::GetElementEnergy( { if (el.Space() == FunctionSpace::Pk) { - return 2 * el.GetOrder() - 1; + return 2 * el.GetOrder() - 2; } else { @@ -3737,7 +5699,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); @@ -3749,12 +5712,229 @@ 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) { 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 348ae0d3..9de8cd77 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" @@ -47,6 +48,180 @@ 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 + 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 +}; + +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: + 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 + friend class MagnetizationSource2DIntegratorMeshRevSens; +}; + +/// Integrator to assemble d(psi^T R)/dX for the MagnetizationSource2DIntegrator +class MagnetizationSource2DIntegratorMeshRevSens + : 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 + 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 +}; + +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 { @@ -128,7 +303,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 @@ -529,7 +704,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 @@ -555,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; @@ -575,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 @@ -929,8 +1116,65 @@ 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] state - the state grid function + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + DCLossFunctionalIntegratorMeshSens(mfem::GridFunction &state, + DCLossFunctionalIntegrator &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 + DCLossFunctionalIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addDomainSensitivityIntegrator( + DCLossFunctionalIntegrator &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 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 { public: @@ -946,7 +1190,7 @@ class DCLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator DCLossFunctionalDistributionIntegrator(mfem::Coefficient &sigma, std::string name = "") - : sigma(sigma), name(name) + : sigma(sigma), name(std::move(name)) { } private: @@ -995,9 +1239,115 @@ class ACLossFunctionalIntegrator : public mfem::NonlinearFormIntegrator mfem::Coefficient σ #ifndef MFEM_THREAD_SAFE mfem::Vector shape; +#endif + friend class ACLossFunctionalIntegratorMeshSens; + friend class ACLossFunctionalIntegratorPeakFluxSens; +}; + +class ACLossFunctionalIntegratorMeshSens : 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 + ACLossFunctionalIntegratorMeshSens(mfem::GridFunction &peak_flux, + ACLossFunctionalIntegrator &integ) + : peak_flux(peak_flux), 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: + /// peak_flux GridFunction + mfem::GridFunction &peak_flux; + /// 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 +}; + +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, + 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); + + 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 + { + output_sens.at("mesh_coords") + .AddDomainIntegrator( + 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); + } +} + class ACLossFunctionalDistributionIntegrator : public mfem::LinearFormIntegrator { public: @@ -1014,7 +1364,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: @@ -1091,6 +1441,118 @@ 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 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 +}; + +inline void addDomainSensitivityIntegrator( + ForceIntegrator3 &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 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 /// method class ForceIntegrator : public mfem::NonlinearFormIntegrator @@ -1229,7 +1691,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(name) + : rho(rho), k_s(k_s), alpha(alpha), beta(beta), name(std::move(name)) { } private: @@ -1247,14 +1709,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: @@ -1273,7 +1853,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(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 342b696f..55672fc8 100644 --- a/src/physics/electromagnetics/electromag_outputs.cpp +++ b/src/physics/electromagnetics/electromag_outputs.cpp @@ -1,13 +1,18 @@ +#include #include -#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 { @@ -15,8 +20,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.data(), space_dim)); auto &v = output.fields.at("vforce").gridFunc(); v = 0.0; @@ -31,20 +38,32 @@ 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(); - mfem::Vector about_vector(&about[0], axis.size()); + + auto space_dim = output.fields.at("vtorque").mesh().SpaceDimension(); + mfem::Vector about_vector(about.data(), 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(); }); @@ -61,36 +80,413 @@ 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); 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) + { + 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); + // 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; + std::cout << "dc_loss_dot = " << dc_loss_dot << "\n"; + return dc_loss_dot; + } + else if (wrt.rfind("rms_current", 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 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) + { + 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) + { + 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 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; + } +} + +// 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) + { + 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) + { + 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) + { + 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) + { + 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)); } } @@ -102,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); @@ -114,53 +513,708 @@ 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) / + 8.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) +{ + 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) / 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) / 8.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) / 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) / 8.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) / 8.0; + double strand_loss_dot = sigma_b2 * M_PI * pow(output.radius, 4) * + 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; + + // 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) / 8.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) / 8.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) / 8.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) / 8.0; + + double strand_loss_dot = + output.stack_length * M_PI * pow(output.radius, 4) * + 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; + + 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 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) / 8.0; + + double strand_loss_dot = + output.stack_length * M_PI * pow(output.radius, 4) * + 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; + + 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; + } +} + +double vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + 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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + pow(output.radius, 4) * + 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.0; + return num_slots_bar; + } + else + { + return 0.0; + } } -void calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial) +void vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) { - calcOutputPartial(output.output, wrt, inputs, partial); + 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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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) / 8.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) / 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) / 8.0; + // double stack_length_bar = strand_loss_bar * sigma_b2 * M_PI * + // pow(output.radius, 4) * + // 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) / 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) / 8.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( std::map &fields, mfem::Coefficient &sigma, const nlohmann::json &options) - : output(fields.at("peak_flux").space(), fields), - volume(fields, options), - fields(fields) + : output(fields.at("peak_flux").space(), fields), volume(fields, options) { if (options.contains("attributes")) { @@ -190,27 +1244,35 @@ 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 calcOutputPartial(output.output, wrt, inputs); + return jacobianVectorProduct(output.output, wrt_dot, wrt); } -void calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial) +double vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt) { - calcOutputPartial(output.output, wrt, inputs, partial); + return vectorJacobianProduct(output.output, out_bar, wrt); +} + +void vectorJacobianProduct(CoreLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt, + mfem::Vector &wrt_bar) +{ + vectorJacobianProduct(output.output, out_bar, wrt, wrt_bar); } CoreLossFunctional::CoreLossFunctional( std::map &fields, const nlohmann::json &components, const nlohmann::json &materials, - const nlohmann::json &options) - : output(fields.at("peak_flux").space(), fields), + 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)), alpha(constructMaterialCoefficient("alpha", components, materials)), @@ -220,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)); + output.addOutputDomainIntegrator(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 eb5c3aad..f40a0389 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: @@ -149,8 +150,21 @@ 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 ForceIntegrator3(nu, fields.at("vtorque").gridFunc(), attrs), + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), + // attrs), + air_attrs); + } + else + { + output.addOutputDomainIntegrator( + new ForceIntegrator3(nu, fields.at("vtorque").gridFunc(), attrs)); + // new ForceIntegrator(nu, fields.at("vtorque").gridFunc(), attrs)); + } } private: @@ -158,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 @@ -215,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 double vectorJacobianProduct(ACLossFunctional &output, + const mfem::Vector &out_bar, + const std::string &wrt); - friend void calcOutputPartial(ACLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial); + 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, @@ -232,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 @@ -258,19 +341,24 @@ class CoreLossFunctional final friend double calcOutput(CoreLossFunctional &output, const MachInputs &inputs); - friend double calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs); + 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 calcOutputPartial(CoreLossFunctional &output, - const std::string &wrt, - const MachInputs &inputs, - mfem::Vector &partial); + 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, 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/magnetic_source_functions.cpp b/src/physics/electromagnetics/magnetic_source_functions.cpp index 56ce41a1..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 @@ -11,7 +12,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 +23,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 +41,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 +59,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 +77,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,162 +130,232 @@ 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, 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()); - north_magnetization(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); + std::array M_a; + north_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + + 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 /// \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, 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()); - south_magnetization(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); + std::array M_a; + south_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + + 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 /// \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, 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()); - cw_magnetization(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); + std::array M_a; + cw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + + 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 /// \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, 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()); - ccw_magnetization(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); + std::array M_a; + ccw_magnetization(vdim, remnant_flux, x_a.data(), M_a.data()); + + 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 /// \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 +363,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 +378,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 +390,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 +403,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 +418,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 +430,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 +443,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 +458,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()); @@ -396,132 +503,148 @@ MagnetizationCoefficient::MagnetizationCoefficient( { if (source == "north") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "south") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "cw") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "ccw") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "x") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "y") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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); })); } } else if (source == "z") { - for (auto &attr : attrs) + for (const auto &attr : attrs) { mag_coeff.addCoefficient( 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..1f19a6c7 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" @@ -86,6 +88,21 @@ 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()); + 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), {}); } void MagnetostaticSolver::addOutput(const std::string &fun, @@ -95,7 +112,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) @@ -145,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); @@ -210,8 +249,12 @@ void MagnetostaticSolver::addOutput(const std::string &fun, std::forward_as_tuple("peak_flux"), std::forward_as_tuple(mesh(), dg_field_options)); - CoreLossFunctional out( - fields, AbstractSolver2::options["components"], materials, options); + 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) @@ -259,6 +302,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/electromagnetics/magnetostatic_residual.cpp b/src/physics/electromagnetics/magnetostatic_residual.cpp index 87c0d605..5efb8760 100644 --- a/src/physics/electromagnetics/magnetostatic_residual.cpp +++ b/src/physics/electromagnetics/magnetostatic_residual.cpp @@ -2,11 +2,16 @@ #include #include "adept.h" +#include "electromag_integ.hpp" +#include "mach_linearform.hpp" +#include "mach_load.hpp" #include "mfem.hpp" #include "nlohmann/json.hpp" #include "mach_input.hpp" #include "magnetostatic_load.hpp" +#include "mfem_common_integ.hpp" +#include "utils.hpp" #include "magnetostatic_residual.hpp" @@ -16,13 +21,38 @@ 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 +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 { @@ -34,13 +64,17 @@ 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); + if (residual.current_coeff != nullptr) + { + setInputs(*residual.current_coeff, 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 +82,12 @@ 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); + if (residual.current_coeff != nullptr) + { + setInputs(*residual.current_coeff, inputs); + } + addLoad(*residual.load, res_vec); // mfem::Vector state; // setVectorFromInputs(inputs, "state", state); @@ -88,12 +126,21 @@ 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) { 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; } @@ -102,16 +149,51 @@ 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); + + residual.scratch.SetSize(res_dot.Size()); + residual.scratch = 0.0; + addLoad(*residual.load, residual.scratch); + residual.current_coeff->resetCurrentDensityFromCache(); + + 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); - jacobianVectorProduct(residual.load, wrt_dot, wrt, res_dot); + jacobianVectorProduct(*residual.load, wrt_dot, wrt, res_dot); } 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); + + residual.scratch.SetSize(res_bar.Size()); + residual.scratch = 0.0; + addLoad(*residual.load, residual.scratch); + residual.current_coeff->resetCurrentDensityFromCache(); + + 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); + wrt_bar += vectorJacobianProduct(*residual.load, res_bar, wrt); return wrt_bar; } @@ -121,7 +203,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 +219,66 @@ 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(); + // auto space_dim = mesh->Dimension(); + 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); + if (options.contains("current")) + { + 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 mach::DomainLFIntegrator(*current_coeff), current_attrs); + } + 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)); + } + else + { + 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 103bb7d4..14e8d98c 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" @@ -49,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); @@ -80,9 +88,17 @@ 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; + std::unique_ptr mag_coeff; + std::unique_ptr nuM; + /// preconditioner for inverting residual's state Jacobian std::unique_ptr prec; + + /// Work vector + mfem::Vector scratch; }; } // namespace mach diff --git a/src/physics/electromagnetics/reluctivity_coefficient.cpp b/src/physics/electromagnetics/reluctivity_coefficient.cpp index 4ccf9df6..13a3d8af 100644 --- a/src/physics/electromagnetics/reluctivity_coefficient.cpp +++ b/src/physics/electromagnetics/reluctivity_coefficient.cpp @@ -1,59 +1,296 @@ +#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; + + 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, + 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; + /// spline representing d2log(nu)/dB2 + std::unique_ptr d2lognudb2; +}; + +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) +{ + 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")) + /// 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; } @@ -103,7 +340,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) @@ -113,7 +350,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)); @@ -123,3 +360,341 @@ 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 (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, +// 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/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" 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.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_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.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/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_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/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/src/physics/mach_nonlinearform.cpp b/src/physics/mach_nonlinearform.cpp index 8adec254..ba8d6727 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" @@ -72,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); @@ -102,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; } @@ -109,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; } @@ -129,14 +144,17 @@ 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); - auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); + const auto &ess_tdof_list = form.nf.GetEssentialTrueDofs(); if (ess_tdof_list.Size() == 0) { return; } + adj_solver.Mult(state_bar, adjoint); auto *hypre_jac_e = form.jac_e.As(); @@ -147,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 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..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; @@ -12,7 +15,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) @@ -21,7 +40,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); } @@ -30,51 +49,1196 @@ 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 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 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; + + 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; + + /// 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(trans, ip); + + /// val = w * s; + double s_bar = val_bar * w; + w_bar += val_bar * s; + + /// double s = rho->Eval(trans, ip); + rho->EvalRevDiff(s_bar, 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 = 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) { const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); ++i) - { - const auto &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - el.CalcShape(ip, shape); - fun += ip.weight * (shape * elfun) * trans.Weight(); + double fun = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) + { + const auto &ip = ir->IntPoint(i); + trans.SetIntPoint(&ip); + el.CalcShape(ip, shape); + fun += ip.weight * (shape * elfun) * trans.Weight(); + } + return fun; +} + +double MagnitudeCurlStateIntegrator::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ + /// 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); + + 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); + } + + double fun = 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; + } + 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) +{ + if (options.contains("rho")) + { + integ.rho = options["rho"].get(); + } +} + +void setInputs(IEAggregateIntegratorNumerator &integ, const MachInputs &inputs) +{ + setValueFromInputs(inputs, "true_max", integ.true_max); +} + +double IEAggregateIntegratorNumerator::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ +#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()); + + double fun = 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; + } + 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) +{ + if (options.contains("rho")) + { + integ.rho = options["rho"].get(); + } +} + +void setInputs(IEAggregateIntegratorDenominator &integ, + const MachInputs &inputs) +{ + setValueFromInputs(inputs, "true_max", integ.true_max); +} + +double IEAggregateIntegratorDenominator::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ +#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()); + + double fun = 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; + } + 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) +{ + if (options.contains("rho")) + { + integ.rho = options["rho"].get(); + } +} + +double IECurlMagnitudeAggregateIntegratorNumerator::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ + 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); + + 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); + + 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; + } + 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) +{ + if (options.contains("rho")) + { + integ.rho = options["rho"].get(); + } +} + +double IECurlMagnitudeAggregateIntegratorDenominator::GetElementEnergy( + const mfem::FiniteElement &el, + mfem::ElementTransformation &trans, + const mfem::Vector &elfun) +{ + 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); + + 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 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; } return fun; } -double MagnitudeCurlStateIntegrator::GetElementEnergy( +void IECurlMagnitudeAggregateIntegratorDenominator::AssembleElementVector( const mfem::FiniteElement &el, mfem::ElementTransformation &trans, - const mfem::Vector &elfun) + const mfem::Vector &elfun, + mfem::Vector &elfun_bar) { - /// 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; + mfem::DenseMatrix curlshape(ndof, curl_dim); + mfem::DenseMatrix curlshape_dFt(ndof, curl_dim); #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); + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); - const auto *ir = IntRule; + 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() - 1; + return 2 * el.GetOrder() - 2; } else { @@ -85,212 +1249,223 @@ double MagnitudeCurlStateIntegrator::GetElementEnergy( ir = &IntRules.Get(el.GetGeomType(), order); } - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); i++) + elfun_bar.SetSize(elfun.Size()); + elfun_bar = 0.0; + for (int i = 0; i < ir->GetNPoints(); ++i) { - b_vec = 0.0; - const IntegrationPoint &ip = ir->IntPoint(i); - + const auto &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 (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(); - const double b_mag = b_vec_norm / trans.Weight(); - fun += b_mag * w; - } - return fun; -} + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; -void setOptions(IEAggregateIntegratorNumerator &integ, - const nlohmann::json &options) -{ - if (options.contains("rho")) - { - integ.rho = options["rho"].get(); - } -} + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); + // fun += exp_rho_curl_mag * w; -double IEAggregateIntegratorNumerator::GetElementEnergy( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun) -{ -#ifdef MFEM_THREAD_SAFE - mfem::Vector shape(elfun.Size()); -#else - shape.SetSize(elfun.Size()); -#endif + /// Start reverse pass... + /// fun += exp_rho_curl_mag * w; + double fun_bar = 1.0; - const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + 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; - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); ++i) - { - const auto &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - el.CalcShape(ip, shape); - double g = shape * elfun; - fun += ip.weight * trans.Weight() * g * exp(rho * g); - } - return fun; -} + /// 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; -void setOptions(IEAggregateIntegratorDenominator &integ, - const nlohmann::json &options) -{ - if (options.contains("rho")) - { - integ.rho = options["rho"].get(); - } -} + /// 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); -double IEAggregateIntegratorDenominator::GetElementEnergy( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun) -{ -#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()); + /// 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); - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); ++i) - { - const auto &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); - el.CalcShape(ip, shape); - double g = shape * elfun; - fun += ip.weight * trans.Weight() * exp(rho * g); + /// curlshape_dFt.AddMultTranspose(elfun, curl_vec); + curlshape_dFt.AddMult(curl_vec_bar, elfun_bar); } - return fun; } -void setOptions(IECurlMagnitudeAggregateIntegratorNumerator &integ, - const nlohmann::json &options) +void IECurlMagnitudeAggregateIntegratorDenominatorMeshSens:: + AssembleRHSElementVect(const FiniteElement &mesh_el, + ElementTransformation &mesh_trans, + Vector &mesh_coords_bar) { - if (options.contains("rho")) + 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) { - integ.rho = options["rho"].get(); + dof_tr->InvTransformPrimal(elfun); } -} - -double IECurlMagnitudeAggregateIntegratorNumerator::GetElementEnergy( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun) -{ - int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; #ifdef MFEM_THREAD_SAFE - mfem::DenseMatrix curlshape(ndof, dimc); - mfem::DenseMatrix curlshape_dFt(ndof, dimc); + DenseMatrix curlshape; + DenseMatrix curlshape_dFt; + DenseMatrix curlshape_dFt_bar; + DenseMatrix PointMat_bar; #else - curlshape.SetSize(ndof, dimc); - curlshape_dFt.SetSize(ndof, dimc); + 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, dimc); + double curl_vec_buffer[3] = {}; + Vector curl_vec(curl_vec_buffer, curl_dim); - const auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + double curl_vec_bar_buffer[3] = {}; + Vector curl_vec_bar(curl_vec_bar_buffer, curl_dim); - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); ++i) + // cast the ElementTransformation + auto &isotrans = dynamic_cast(trans); + + const IntegrationRule *ir = IntRule; + if (ir == nullptr) { - curl_vec = 0.0; + 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); - 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(); - fun += ip.weight * trans.Weight() * curl_mag * exp(rho * curl_mag); - } - return fun; -} + curlshape_dFt.MultTranspose(elfun, curl_vec); + const double curl_vec_norm = curl_vec.Norml2(); + const double curl_mag = curl_vec_norm / trans_weight; -void setOptions(IECurlMagnitudeAggregateIntegratorDenominator &integ, - const nlohmann::json &options) -{ - if (options.contains("rho")) - { - integ.rho = options["rho"].get(); - } -} + const double exp_rho_curl_mag = exp(rho * (curl_mag / actual_max)); -double IECurlMagnitudeAggregateIntegratorDenominator::GetElementEnergy( - const mfem::FiniteElement &el, - mfem::ElementTransformation &trans, - const mfem::Vector &elfun) -{ - int ndof = el.GetDof(); - int dim = el.GetDim(); - int dimc = (dim == 3) ? 3 : 1; + // fun += exp_rho_curl_mag * w; -#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); -#endif + /// Start reverse pass... + /// fun += exp_rho_curl_mag * w; + double fun_bar = 1.0; - double curl_vec_buffer[3]; - Vector curl_vec(curl_vec_buffer, dimc); + 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 auto *ir = &IntRules.Get(el.GetGeomType(), 2 * el.GetOrder()); + /// 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; - double fun = 0.0; - for (int i = 0; i < ir->GetNPoints(); ++i) - { - curl_vec = 0.0; + /// 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 auto &ip = ir->IntPoint(i); - trans.SetIntPoint(&ip); + /// 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) { - el.CalcCurlShape(ip, curlshape); - MultABt(curlshape, trans.Jacobian(), curlshape_dFt); + /// 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 + else // Dealing with scalar H1 field representing Az { - el.CalcCurlShape(ip, curlshape_dFt); + /// 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); } - curlshape_dFt.AddMultTranspose(elfun, curl_vec); - const double curl_vec_norm = curl_vec.Norml2(); - const double curl_mag = curl_vec_norm / trans.Weight(); - fun += ip.weight * trans.Weight() * exp(rho * curl_mag); + /// 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); + } + } } - return fun; } void DiffusionIntegratorMeshSens::AssembleRHSElementVect( @@ -1197,6 +2372,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 7d212437..08c79881 100644 --- a/src/physics/mfem_common_integ.hpp +++ b/src/physics/mfem_common_integ.hpp @@ -1,10 +1,12 @@ #ifndef MACH_MFEM_COMMON_INTEG #define MACH_MFEM_COMMON_INTEG +#include #include "mfem.hpp" #include "nlohmann/json.hpp" #include "finite_element_state.hpp" +#include "mach_input.hpp" #include "mach_integrator.hpp" namespace mach @@ -16,13 +18,79 @@ 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] state - the state grid function + /// \param[in] integ - reference to primal integrator that holds inputs for + /// integrator + VolumeIntegratorMeshSens(mfem::GridFunction &state, VolumeIntegrator &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 + VolumeIntegrator &integ; + +#ifndef MFEM_THREAD_SAFE + mfem::DenseMatrix PointMat_bar; +#endif +}; + +inline void addDomainSensitivityIntegrator( + VolumeIntegrator &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 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 { public: @@ -43,53 +111,278 @@ 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 addDomainSensitivityIntegrator( + MagnitudeCurlStateIntegrator &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 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 { public: 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 { @@ -97,20 +390,95 @@ 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 integrator + 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 addDomainSensitivityIntegrator( + IECurlMagnitudeAggregateIntegratorNumerator &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 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 : public mfem::NonlinearFormIntegrator { @@ -118,21 +486,95 @@ 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 addDomainSensitivityIntegrator( + IECurlMagnitudeAggregateIntegratorDenominator &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 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 { public: @@ -453,6 +895,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 cccda0f4..223483b3 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" @@ -149,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 @@ -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) @@ -339,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 (const 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].merge_patch(material); + } + } + } + fields.emplace( "state", FiniteElementState(mesh(), options["space-dis"], num_states, "state")); @@ -352,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) @@ -473,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 @@ -498,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 diff --git a/src/physics/pde_solver.hpp b/src/physics/pde_solver.hpp index 4304bdaa..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 @@ -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: @@ -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.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/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 3852119a..366b9519 100644 --- a/src/utils/irrotational_projector.cpp +++ b/src/utils/irrotational_projector.cpp @@ -21,6 +21,17 @@ IrrotationalProjector::IrrotationalProjector(ParFiniteElementSpace &h1_fes, div_x(&h1_fes), pcg(h1_fes.GetComm()) { + psi = 0.0; // NOLINT + amg.SetPrintLevel(0); + + pcg.SetRelTol(1e-14); + pcg.SetAbsTol(1e-14); + pcg.SetMaxIter(200); + pcg.SetKDim(200); + pcg.SetPrintLevel( + IterativeSolver::PrintLevel().Warnings().Errors().Iterations().All()); + 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 +80,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 +120,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 +153,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 +179,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..5f51f3a8 100644 --- a/src/utils/irrotational_projector.hpp +++ b/src/utils/irrotational_projector.hpp @@ -98,7 +98,9 @@ class IrrotationalProjector : public mfem::Operator mutable mfem::Vector RHS; mutable mfem::HypreBoomerAMG amg; - mutable mfem::HyprePCG pcg; + // mutable mfem::HyprePCG pcg; + mutable mfem::GMRESSolver pcg; + // mutable mfem::CGSolver pcg; mfem::Array ess_bdr, ess_bdr_tdofs; }; diff --git a/src/utils/l2_transfer_operator.cpp b/src/utils/l2_transfer_operator.cpp index 62f3c1c5..0139682c 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" @@ -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(); @@ -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,21 @@ 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 +395,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 +416,23 @@ 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(); @@ -455,9 +483,9 @@ 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; + int curl_dim = space_dim; int mesh_dof = mesh_fe.GetDof(); int state_dof = state_fe.GetDof(); @@ -465,6 +493,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 +512,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 +524,19 @@ 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 +576,55 @@ 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) @@ -572,12 +650,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 +664,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 +694,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 +710,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 +744,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 +765,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, @@ -686,19 +785,20 @@ class CurlMagnitudeOperator : public mach::L2TransferOperation mfem::Vector &mesh_coords_bar) const override { auto &isotrans = dynamic_cast(trans); - auto &mesh_fe = *isotrans.GetFE(); - int space_dim = isotrans.GetSpaceDim(); - int curl_dim = space_dim == 3 ? 3 : 1; + const auto &mesh_fe = *isotrans.GetFE(); - 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 +813,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 +859,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) @@ -784,6 +909,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 @@ -852,7 +1036,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); } @@ -860,7 +1044,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); } @@ -905,13 +1089,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); } @@ -920,7 +1104,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); } @@ -964,13 +1148,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); } @@ -983,7 +1167,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/l2_transfer_operator.hpp b/src/utils/l2_transfer_operator.hpp index 3c15db54..fadadc39 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,16 @@ 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 +217,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 +232,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/src/utils/mesh_warper/mesh_warper.cpp b/src/utils/mesh_warper/mesh_warper.cpp index e9e17e5b..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); @@ -93,7 +99,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 +137,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]) = @@ -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) @@ -182,7 +197,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 +220,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]); @@ -250,40 +265,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/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 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); diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index 509177b7..e9932e74 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -20,10 +20,13 @@ 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 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_box.cpp b/test/regression/test_magnetostatic_box.cpp index 1da50cb0..a1eb225e 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,21 +43,25 @@ auto options = R"( }, "nonlin-solver": { "type": "newton", - "printlevel": 3, + "printlevel": 1, "maxiter": 15, "reltol": 1e-10, "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": { @@ -127,8 +131,18 @@ 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); @@ -141,6 +155,10 @@ TEST_CASE("Magnetostatic Box Solver Regression Test", 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); } } } @@ -202,14 +220,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) @@ -221,5 +239,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 new file mode 100644 index 00000000..f4aa7bd1 --- /dev/null +++ b/test/regression/test_magnetostatic_box2d.cpp @@ -0,0 +1,214 @@ +#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": { + "box1": { + "attrs": [1], + "material": { + "name": "box1", + "mu_r": 795774.7154594767 + } + }, + "box2": { + "attrs": [2], + "material": { + "name": "box2", + "mu_r": 795774.7154594767 + } + } + }, + "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.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 + }; + + // // 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]); + // 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; +} 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/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; 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/CMakeLists.txt b/test/unit/CMakeLists.txt index aeeecda7..f54ef619 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 @@ -71,8 +72,10 @@ set(EM_MPI_TEST_SRCS test_pde_solver test_data_logging test_l2_transfer_operator + 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/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_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_common_outputs.cpp b/test/unit/test_common_outputs.cpp index 12bab6e8..2eab6e8e 100644 --- a/test/unit/test_common_outputs.cpp +++ b/test/unit/test_common_outputs.cpp @@ -1,11 +1,419 @@ #include #include +#include +#include #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("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; @@ -28,6 +436,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 +479,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()); @@ -91,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); @@ -110,6 +715,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 +743,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); diff --git a/test/unit/test_electromag_integ.cpp b/test/unit/test_electromag_integ.cpp index 40979862..88ca0347 100644 --- a/test/unit/test_electromag_integ.cpp +++ b/test/unit/test_electromag_integ.cpp @@ -6,6 +6,224 @@ #include "electromag_integ.hpp" #include "electromag_test_data.hpp" +TEST_CASE("NonlinearDiffusionIntegrator::AssembleElementGrad") +{ + 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); + FunctionCoefficient pert(randState); + state.ProjectCoefficient(pert); + + NonlinearForm res(&fes); + res.AddDomainIntegrator(new mach::NonlinearDiffusionIntegrator(nu)); + + // initialize the vector that the Jacobian multiplies + GridFunction v(&fes); + v.ProjectCoefficient(pert); + + // evaluate the Jacobian and compute its product with v + 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); + 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); + + for (int i = 0; i < jac_v.Size(); ++i) + { + REQUIRE(jac_v(i) == Approx(jac_v_fd(i)).margin(1e-6)); + } + } + } +} + +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("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]") { @@ -770,6 +988,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; @@ -798,8 +1066,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) { @@ -826,6 +1092,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; @@ -881,20 +1205,19 @@ TEST_CASE("MagneticEnergyIntegrator::AssembleElementVector") } } -TEST_CASE("MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect") +TEST_CASE("MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect - 2D") { using namespace mfem; using namespace electromag_data; - const int dim = 3; double delta = 1e-5; - // generate a 6 element mesh + // generate a 8 element mesh int num_edge = 2; - auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - Element::TETRAHEDRON, - 2.0, 3.0, 1.0, true); + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); mesh.EnsureNodes(); + const auto dim = mesh.SpaceDimension(); NonLinearCoefficient nu; @@ -902,21 +1225,93 @@ TEST_CASE("MagneticEnergyIntegratorMeshSens::AssembleRHSElementVect") { DYNAMIC_SECTION("...for degree p = " << p) { - ND_FECollection fec(p, dim); + 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 pert(3, randVectorState); - v.ProjectCoefficient(pert); + VectorFunctionCoefficient v_pert(dim, randVectorState); + v.ProjectCoefficient(v_pert); - // initialize state; here we randomly perturb a constant state - GridFunction A(&fes); - A.ProjectCoefficient(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; + using namespace electromag_data; + + const int dim = 3; + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON, + 2.0, 3.0, 1.0, true); + mesh.EnsureNodes(); + + NonLinearCoefficient 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::MagneticEnergyIntegrator(nu); NonlinearForm functional(&fes); @@ -1686,24 +2081,275 @@ TEST_CASE("calcMagneticEnergyDoubleDot") } } -TEST_CASE("ForceIntegrator::GetElementEnergy") +TEST_CASE("DCLossFunctionalIntegratorMeshSens::AssembleRHSElementVect (2D)") { using namespace mfem; using namespace electromag_data; - const int dim = 3; // templating is hard here because mesh constructors double delta = 1e-5; - // generate a 6 element mesh + // 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(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("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("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; + 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, - 2.0, 3.0, 1.0, true); + 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) @@ -1717,27 +2363,27 @@ TEST_CASE("ForceIntegrator::GetElementEnergy") // create v displacement field GridFunction v(&mesh_fes); - VectorFunctionCoefficient pert(3, randVectorState); + VectorFunctionCoefficient pert(dim, randVectorState); v.ProjectCoefficient(pert); // initialize state; here we randomly perturb a constant state - GridFunction A(&fes); - A.ProjectCoefficient(pert); + GridFunction a(&fes); + a.ProjectCoefficient(pert); NonlinearForm functional(&fes); functional.AddDomainIntegrator( - new mach::ForceIntegrator(nu, v)); + new mach::ForceIntegrator3(nu, v)); - auto force = functional.GetEnergy(A); + 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); + auto dWds = -energy.GetEnergy(a); add(x_nodes, 2*delta, v, x_nodes); - dWds += energy.GetEnergy(A); + dWds += energy.GetEnergy(a); dWds /= 2*delta; // std::cout << "-dWds: " << -dWds << " Force: " << force << "\n"; @@ -1746,25 +2392,422 @@ TEST_CASE("ForceIntegrator::GetElementEnergy") } } -TEST_CASE("ForceIntegrator::AssembleElementVector") +TEST_CASE("ForceIntegrator3::GetElementEnergy - 2D") { using namespace mfem; using namespace electromag_data; - const int dim = 3; double delta = 1e-5; - // generate a 6 element mesh + // generate a 8 element mesh int num_edge = 2; - auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, - Element::TETRAHEDRON, - 1.0, 1.0, 1.0, true); + auto mesh = Mesh::MakeCartesian2D(num_edge, num_edge, + Element::TRIANGLE); 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); + 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("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("ForceIntegrator::GetElementEnergy") +{ + using namespace mfem; + using namespace electromag_data; + + const int dim = 3; // templating is hard here because mesh constructors + double delta = 1e-5; + + // generate a 6 element mesh + int num_edge = 2; + auto mesh = Mesh::MakeCartesian3D(num_edge, num_edge, num_edge, + Element::TETRAHEDRON, + 2.0, 3.0, 1.0, true); + mesh.EnsureNodes(); + + NonLinearCoefficient nu; + + /// construct elements + 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); + + NonlinearForm functional(&fes); + functional.AddDomainIntegrator( + new mach::ForceIntegrator(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::AssembleElementVector") +{ + using namespace mfem; + using namespace electromag_data; + + const int dim = 3; + double delta = 1e-5; + + // generate a 6 element mesh + // 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); + mesh.EnsureNodes(); + + NonLinearCoefficient nu; + + /// construct elements + for (int p = 1; p <= 1; ++p) { DYNAMIC_SECTION("...for degree p = " << p) { diff --git a/test/unit/test_electromag_outputs.cpp b/test/unit/test_electromag_outputs.cpp new file mode 100644 index 00000000..11fadc46 --- /dev/null +++ b/test/unit/test_electromag_outputs.cpp @@ -0,0 +1,1275 @@ +#include + +#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 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 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)); + + // 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("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); + + 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 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 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... + inputs["num_turns"] = num_turns + pert * delta; + double dfdp_fd_p = calcOutput(fun, inputs); + + inputs["num_turns"] = num_turns - 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_slots") +{ + 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_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 + 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", 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)); + } + } +} + +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; + 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_l2_transfer_operator.cpp b/test/unit/test_l2_transfer_operator.cpp index de06ee59..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; @@ -1040,6 +1249,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 +1461,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)); +} diff --git a/test/unit/test_linesearch.cpp b/test/unit/test_linesearch.cpp new file mode 100644 index 00000000..a4f26ee5 --- /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::CGSolver cg(MPI_COMM_SELF); + // cg.SetPrintLevel(mfem::IterativeSolver::PrintLevel().All()); + newton.SetSolver(cg); + + 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 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} diff --git a/test/unit/test_magnetic_load.cpp b/test/unit/test_magnetic_load.cpp index 300cd7bf..369183a8 100644 --- a/test/unit/test_magnetic_load.cpp +++ b/test/unit/test_magnetic_load.cpp @@ -63,7 +63,16 @@ 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); + 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)); MachInputs inputs; setInputs(load, inputs); @@ -124,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); diff --git a/test/unit/test_mfem_common_integ.cpp b/test/unit/test_mfem_common_integ.cpp index a14b2758..8d117363 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(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("StateIntegrator::GetElementEnergy (3D)") { using namespace mfem; @@ -114,6 +203,660 @@ TEST_CASE("StateIntegrator::GetElementEnergy (3D)") REQUIRE(rms == Approx(sqrt(2)/2).margin(1e-10)); } +TEST_CASE("MagnitudeCurlStateIntegrator::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 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("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; + 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); + 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(); + + 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; @@ -664,6 +1407,101 @@ 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); + 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") 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 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)); + } + } +} +