From 3814810ba3c28a1571a833acbf99d74c8c8feb82 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:47 -0800 Subject: [PATCH 01/21] Tweak build system - export build commands --- CMakeLists.txt | 2 ++ palace/CMakeLists.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bef4b1c0..fe793d0a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,8 @@ # CMake 3.21 was released in Jul. 2021 (required for HIP support) cmake_minimum_required(VERSION 3.21) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Prohibit in-source builds if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-source builds are prohibited") diff --git a/palace/CMakeLists.txt b/palace/CMakeLists.txt index 1d2d0ac6f..97c0ed929 100644 --- a/palace/CMakeLists.txt +++ b/palace/CMakeLists.txt @@ -8,6 +8,8 @@ # CMake 3.21 was released in Jul. 2021 (required for HIP support) cmake_minimum_required(VERSION 3.21) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Prohibit in-source builds if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-source builds are prohibited") From 56efa98f54ed080a5984c34bef681850f89dd5c2 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:47 -0800 Subject: [PATCH 02/21] Add flag for MFEM to throw not abort - Used for testing since Catch does not support testing aborts --- cmake/ExternalMFEM.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/ExternalMFEM.cmake b/cmake/ExternalMFEM.cmake index b75786fa4..1de07a565 100644 --- a/cmake/ExternalMFEM.cmake +++ b/cmake/ExternalMFEM.cmake @@ -76,6 +76,9 @@ if(CMAKE_BUILD_TYPE MATCHES "Debug|debug|DEBUG") endif() endif() +# Replace mfem abort calls with exceptions for testing, default off +set(PALACE_MFEM_USE_EXCEPTIONS CACHE NO "MFEM throw exceptsions instead of abort calls") + set(MFEM_OPTIONS ${PALACE_SUPERBUILD_DEFAULT_ARGS}) list(APPEND MFEM_OPTIONS "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" @@ -91,6 +94,7 @@ list(APPEND MFEM_OPTIONS "-DMFEM_USE_METIS_5=YES" "-DMFEM_USE_CEED=NO" "-DMFEM_USE_SUNDIALS=${PALACE_WITH_SUNDIALS}" + "-DMFEM_USE_EXCEPTIONS=${PALACE_MFEM_USE_EXCEPTIONS}" ) if(PALACE_WITH_STRUMPACK OR PALACE_WITH_MUMPS) list(APPEND MFEM_OPTIONS From 7a403b6185bb9d5857c375765c7880acdbba7347 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:47 -0800 Subject: [PATCH 03/21] Simplify printing using more fmt features --- palace/models/lumpedportoperator.cpp | 116 +++++++----------- palace/models/surfaceconductivityoperator.cpp | 11 +- palace/models/surfacecurrentoperator.cpp | 13 +- palace/models/surfaceimpedanceoperator.cpp | 48 +++----- palace/models/waveportoperator.cpp | 47 ++++--- palace/utils/geodata.cpp | 10 +- 6 files changed, 81 insertions(+), 164 deletions(-) diff --git a/palace/models/lumpedportoperator.cpp b/palace/models/lumpedportoperator.cpp index 7d74e9c9b..b6882c142 100644 --- a/palace/models/lumpedportoperator.cpp +++ b/palace/models/lumpedportoperator.cpp @@ -357,131 +357,99 @@ void LumpedPortOperator::SetUpBoundaryProperties(const IoData &iodata, void LumpedPortOperator::PrintBoundaryInfo(const IoData &iodata, const mfem::ParMesh &mesh) { - // Print out BC info for all port attributes. if (ports.empty()) { return; } - Mpi::Print("\nConfiguring Robin impedance BC for lumped ports at attributes:\n"); + + fmt::memory_buffer buf{}; // Output buffer & buffer append lambda for cleaner code + auto to = [](auto &buf, auto fmt, auto &&...args) + { fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); }; + using VT = IoData::ValueType; + + // Print out BC info for all port attributes, for both active and inactive ports. + to(buf, "\nConfiguring Robin impedance BC for lumped ports at attributes:\n"); for (const auto &[idx, data] : ports) { for (const auto &elem : data.elems) { for (auto attr : elem->GetAttrList()) { - mfem::Vector normal = mesh::GetSurfaceNormal(mesh, attr); - const double Rs = data.R * data.GetToSquare(*elem); - const double Ls = data.L * data.GetToSquare(*elem); - const double Cs = data.C / data.GetToSquare(*elem); - bool comma = false; - Mpi::Print(" {:d}:", attr); - if (std::abs(Rs) > 0.0) - { - Mpi::Print(" Rs = {:.3e} Ω/sq", - iodata.DimensionalizeValue(IoData::ValueType::IMPEDANCE, Rs)); - comma = true; - } - if (std::abs(Ls) > 0.0) + to(buf, " {:d}:", attr); + if (std::abs(data.R) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" Ls = {:.3e} H/sq", - iodata.DimensionalizeValue(IoData::ValueType::INDUCTANCE, Ls)); - comma = true; + double Rs = data.R * data.GetToSquare(*elem); + to(buf, " Rs = {:.3e} Ω/sq,", iodata.DimensionalizeValue(VT::IMPEDANCE, Rs)); } - if (std::abs(Cs) > 0.0) + if (std::abs(data.L) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" Cs = {:.3e} F/sq", - iodata.DimensionalizeValue(IoData::ValueType::CAPACITANCE, Cs)); - comma = true; + double Ls = data.L * data.GetToSquare(*elem); + to(buf, " Ls = {:.3e} H/sq,", iodata.DimensionalizeValue(VT::INDUCTANCE, Ls)); } - if (comma) + if (std::abs(data.C) > 0.0) { - Mpi::Print(","); + double Cs = data.C / data.GetToSquare(*elem); + to(buf, " Cs = {:.3e} F/sq,", iodata.DimensionalizeValue(VT::CAPACITANCE, Cs)); } - if (mesh.SpaceDimension() == 3) - { - Mpi::Print(" n = ({:+.1f}, {:+.1f}, {:+.1f})", normal(0), normal(1), normal(2)); - } - else - { - Mpi::Print(" n = ({:+.1f}, {:+.1f})", normal(0), normal(1)); - } - Mpi::Print("\n"); + to(buf, " n = ({:+.1f})\n", fmt::join(mesh::GetSurfaceNormal(mesh, attr), ",")); } } } - // Print out port info for all ports. - bool first = true; + // Print out port info for all active ports. + fmt::memory_buffer buf_a{}; for (const auto &[idx, data] : ports) { if (!data.active) { continue; } - if (first) - { - Mpi::Print("\nConfiguring lumped port circuit properties:\n"); - first = false; - } - bool comma = false; - Mpi::Print(" Index = {:d}:", idx); + to(buf_a, " Index = {:d}: ", idx); if (std::abs(data.R) > 0.0) { - Mpi::Print(" R = {:.3e} Ω", - iodata.DimensionalizeValue(IoData::ValueType::IMPEDANCE, data.R)); - comma = true; + to(buf_a, "R = {:.3e} Ω,", iodata.DimensionalizeValue(VT::IMPEDANCE, data.R)); } if (std::abs(data.L) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" L = {:.3e} H", - iodata.DimensionalizeValue(IoData::ValueType::INDUCTANCE, data.L)); - comma = true; + to(buf_a, "L = {:.3e} H,", iodata.DimensionalizeValue(VT::INDUCTANCE, data.L)); } if (std::abs(data.C) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" C = {:.3e} F", - iodata.DimensionalizeValue(IoData::ValueType::CAPACITANCE, data.C)); + to(buf_a, "C = {:.3e} F,", iodata.DimensionalizeValue(VT::CAPACITANCE, data.C)); } - Mpi::Print("\n"); + buf_a.resize(buf_a.size() - 1); // Remove last "," + to(buf_a, "\n"); + } + if (buf_a.size() > 0) + { + to(buf, "\nConfiguring lumped port circuit properties:\n"); + buf.append(buf_a); + buf_a.clear(); } // Print some information for excited lumped ports. - first = true; for (const auto &[idx, data] : ports) { if (!data.excitation) { continue; } - if (first) - { - Mpi::Print("\nConfiguring lumped port excitation source term at attributes:\n"); - first = false; - } + for (const auto &elem : data.elems) { for (auto attr : elem->GetAttrList()) { - Mpi::Print(" {:d}: Index = {:d}\n", attr, idx); + to(buf_a, " {:d}: Index = {:d}\n", attr, idx); } } } + if (buf_a.size() > 0) + { + to(buf, "\nConfiguring lumped port excitation source term at attributes:\n"); + buf.append(buf_a); + } + + Mpi::Print("{}", fmt::to_string(buf)); } const LumpedPortData &LumpedPortOperator::GetPort(int idx) const diff --git a/palace/models/surfaceconductivityoperator.cpp b/palace/models/surfaceconductivityoperator.cpp index 80f9b231a..ad01bbf06 100644 --- a/palace/models/surfaceconductivityoperator.cpp +++ b/palace/models/surfaceconductivityoperator.cpp @@ -119,7 +119,6 @@ void SurfaceConductivityOperator::PrintBoundaryInfo(const IoData &iodata, { for (auto attr : bdr.attr_list) { - mfem::Vector normal = mesh::GetSurfaceNormal(mesh, attr); Mpi::Print(" {:d}: σ = {:.3e} S/m", attr, iodata.DimensionalizeValue(IoData::ValueType::CONDUCTIVITY, bdr.sigma)); if (bdr.h > 0.0) @@ -127,15 +126,7 @@ void SurfaceConductivityOperator::PrintBoundaryInfo(const IoData &iodata, Mpi::Print(", h = {:.3e} m", iodata.DimensionalizeValue(IoData::ValueType::LENGTH, bdr.h)); } - if (mesh.SpaceDimension() == 3) - { - Mpi::Print(", n = ({:+.1f}, {:+.1f}, {:+.1f})", normal(0), normal(1), normal(2)); - } - else - { - Mpi::Print(", n = ({:+.1f}, {:+.1f})", normal(0), normal(1)); - } - Mpi::Print("\n"); + Mpi::Print(", n = ({:+.1f})\n", fmt::join(mesh::GetSurfaceNormal(mesh, attr), ",")); } } } diff --git a/palace/models/surfacecurrentoperator.cpp b/palace/models/surfacecurrentoperator.cpp index ad0138c06..55086d2a0 100644 --- a/palace/models/surfacecurrentoperator.cpp +++ b/palace/models/surfacecurrentoperator.cpp @@ -103,17 +103,8 @@ void SurfaceCurrentOperator::PrintBoundaryInfo(const IoData &iodata, { for (auto attr : elem->GetAttrList()) { - mfem::Vector normal = mesh::GetSurfaceNormal(mesh, attr); - Mpi::Print(" {:d}: Index = {:d}", attr, idx); - if (mesh.SpaceDimension() == 3) - { - Mpi::Print(", n = ({:+.1f}, {:+.1f}, {:+.1f})", normal(0), normal(1), normal(2)); - } - else - { - Mpi::Print(", n = ({:+.1f}, {:+.1f})", normal(0), normal(1)); - } - Mpi::Print("\n"); + Mpi::Print(" {:d}: Index = {:d}, n = ({:+.1f})\n", attr, idx, + fmt::join(mesh::GetSurfaceNormal(mesh, attr), ",")); } } } diff --git a/palace/models/surfaceimpedanceoperator.cpp b/palace/models/surfaceimpedanceoperator.cpp index bf885b23a..fc1439a26 100644 --- a/palace/models/surfaceimpedanceoperator.cpp +++ b/palace/models/surfaceimpedanceoperator.cpp @@ -98,55 +98,35 @@ void SurfaceImpedanceOperator::PrintBoundaryInfo(const IoData &iodata, { return; } - Mpi::Print("\nConfiguring Robin impedance BC at attributes:\n"); + + fmt::memory_buffer buf{}; // Output buffer & buffer append lambda for cleaner code + auto to = [&buf](auto fmt, auto &&...args) + { fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); }; + + using VT = IoData::ValueType; + + to("\nConfiguring Robin impedance BC at attributes:\n"); for (const auto &bdr : boundaries) { for (auto attr : bdr.attr_list) { - mfem::Vector normal = mesh::GetSurfaceNormal(mesh, attr); - bool comma = false; - Mpi::Print(" {:d}:", attr); + to(" {:d}:", attr); if (std::abs(bdr.Rs) > 0.0) { - Mpi::Print(" Rs = {:.3e} Ω/sq", - iodata.DimensionalizeValue(IoData::ValueType::IMPEDANCE, bdr.Rs)); - comma = true; + to(" Rs = {:.3e} Ω/sq,", iodata.DimensionalizeValue(VT::IMPEDANCE, bdr.Rs)); } if (std::abs(bdr.Ls) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" Ls = {:.3e} H/sq", - iodata.DimensionalizeValue(IoData::ValueType::INDUCTANCE, bdr.Ls)); - comma = true; + to(" Ls = {:.3e} H/sq,", iodata.DimensionalizeValue(VT::INDUCTANCE, bdr.Ls)); } if (std::abs(bdr.Cs) > 0.0) { - if (comma) - { - Mpi::Print(","); - } - Mpi::Print(" Cs = {:.3e} F/sq", - iodata.DimensionalizeValue(IoData::ValueType::CAPACITANCE, bdr.Cs)); - comma = true; - } - if (comma) - { - Mpi::Print(","); - } - if (mesh.SpaceDimension() == 3) - { - Mpi::Print(" n = ({:+.1f}, {:+.1f}, {:+.1f})", normal(0), normal(1), normal(2)); + to(" Cs = {:.3e} F/sq,", iodata.DimensionalizeValue(VT::CAPACITANCE, bdr.Cs)); } - else - { - Mpi::Print(" n = ({:+.1f}, {:+.1f})", normal(0), normal(1)); - } - Mpi::Print("\n"); + to(" n = ({:+.1f})\n", fmt::join(mesh::GetSurfaceNormal(mesh, attr), ",")); } } + Mpi::Print("{}", fmt::to_string(buf)); } mfem::Array SurfaceImpedanceOperator::GetAttrList() const diff --git a/palace/models/waveportoperator.cpp b/palace/models/waveportoperator.cpp index 30ef1ad05..84ef02b38 100644 --- a/palace/models/waveportoperator.cpp +++ b/palace/models/waveportoperator.cpp @@ -1159,58 +1159,53 @@ void WavePortOperator::SetUpBoundaryProperties(const IoData &iodata, void WavePortOperator::PrintBoundaryInfo(const IoData &iodata, const mfem::ParMesh &mesh) { - // Print out BC info for all port attributes. if (ports.empty()) { return; } - bool first = true; + fmt::memory_buffer buf{}; // Output buffer & buffer append lambda for cleaner code + auto to = [&buf](auto fmt, auto &&...args) + { fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); }; + using VT = IoData::ValueType; + + // Print out BC info for all active port attributes. for (const auto &[idx, data] : ports) { if (!data.active) { continue; } - if (first) - { - Mpi::Print("\nConfiguring Robin impedance BC for wave ports at attributes:\n"); - first = false; - } for (auto attr : data.GetAttrList()) { - const mfem::Vector &normal = data.port_normal; - Mpi::Print(" {:d}: Index = {:d}, mode = {:d}, d = {:.3e} m", attr, idx, data.mode_idx, - iodata.DimensionalizeValue(IoData::ValueType::LENGTH, data.d_offset)); - if (mesh.SpaceDimension() == 3) - { - Mpi::Print(", n = ({:+.1f}, {:+.1f}, {:+.1f})", normal(0), normal(1), normal(2)); - } - else - { - Mpi::Print(", n = ({:+.1f}, {:+.1f})", normal(0), normal(1)); - } - Mpi::Print("\n"); + to(" {:d}: Index = {:d}, mode = {:d}, d = {:.3e} m, n = ({:+.1f})\n", attr, idx, + data.mode_idx, iodata.DimensionalizeValue(VT::LENGTH, data.d_offset), + fmt::join(data.port_normal, ",")); } } + if (buf.size() > 0) + { + Mpi::Print("\nConfiguring Robin impedance BC for wave ports at attributes:\n"); + Mpi::Print("{}", fmt::to_string(buf)); + buf.clear(); + } // Print some information for excited wave ports. - first = true; for (const auto &[idx, data] : ports) { if (!data.excitation) { continue; } - if (first) - { - Mpi::Print("\nConfiguring wave port excitation source term at attributes:\n"); - first = false; - } for (auto attr : data.GetAttrList()) { - Mpi::Print(" {:d}: Index = {:d}\n", attr, idx); + to(" {:d}: Index = {:d}\n", attr, idx); } } + if (buf.size() > 0) + { + Mpi::Print("\nConfiguring wave port excitation source term at attributes:\n"); + Mpi::Print("{}", fmt::to_string(buf)); + } } const WavePortData &WavePortOperator::GetPort(int idx) const diff --git a/palace/utils/geodata.cpp b/palace/utils/geodata.cpp index f2829e4b1..56516e5c6 100644 --- a/palace/utils/geodata.cpp +++ b/palace/utils/geodata.cpp @@ -1570,15 +1570,7 @@ mfem::Vector GetSurfaceNormal(const mfem::ParMesh &mesh, const mfem::Array if constexpr (false) { - if (dim == 3) - { - Mpi::Print(comm, " Surface normal = ({:+.3e}, {:+.3e}, {:+.3e})", normal(0), - normal(1), normal(2)); - } - else - { - Mpi::Print(comm, " Surface normal = ({:+.3e}, {:+.3e})", normal(0), normal(1)); - } + Mpi::Print(comm, " Surface normal = ({:+.3e})", fmt::join(normal, ", ")); } return normal; From c9a84de47e801ca04d6da9f4e9cedc2f7d31ecaf Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:48 -0800 Subject: [PATCH 04/21] Add small table class for csv printing - Add tests - Add table wrapper for csv files for open-write-close - Make empty cell NULL --- palace/utils/tablecsv.hpp | 322 ++++++++++++++++++++++++++++++++++++ test/unit/CMakeLists.txt | 1 + test/unit/test-tablecsv.cpp | 117 +++++++++++++ 3 files changed, 440 insertions(+) create mode 100644 palace/utils/tablecsv.hpp create mode 100644 test/unit/test-tablecsv.cpp diff --git a/palace/utils/tablecsv.hpp b/palace/utils/tablecsv.hpp new file mode 100644 index 000000000..e3ab6afc0 --- /dev/null +++ b/palace/utils/tablecsv.hpp @@ -0,0 +1,322 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef PALACE_UTILS_TABLECSV_HPP +#define PALACE_UTILS_TABLECSV_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace palace +{ + +struct ColumnOptions +{ + size_t min_left_padding = 8; + size_t float_precision = 9; + std::string empty_cell_val = {"NULL"}; + std::string fmt_sign = {"+"}; +}; + +class Column +{ + friend class Table; + + // View to default options in table class, will be set when columns are added to Table + ColumnOptions *defaults = nullptr; + + // ---- + + // Map-like index, to interface via Table class + std::string name = ""; + +public: + [[nodiscard]] size_t col_width() const + { + size_t pad = min_left_padding.value_or(defaults->min_left_padding); + size_t prec = float_precision.value_or(defaults->float_precision); + + // Normal float in our exponent format needs float_precision + 7 ("+" , leading digit, + // ".", "e", "+", +2 exponent. Sometimes exponent maybe +3 if very small or large; see + // std::numeric_limits::max_exponent. We pick +7 for consistnacy, but + // min_left_padding should be at least 1, which is not currently enforced. + return std::max(pad + prec + 7, header_text.size()); + } + + [[nodiscard]] auto format_header(const std::optional &width = {}) const + { + auto w = width.value_or(col_width()); + return fmt::format("{:>{width}s}", header_text, fmt::arg("width", w)); + } + + [[nodiscard]] auto format_row(size_t i, const std::optional &width = {}) const + { + auto width_ = width.value_or(col_width()); + // If data available format double + if ((i >= 0) && (i < data.size())) + { + auto val = data[i]; + auto sign = fmt_sign.value_or(defaults->fmt_sign); + auto prec = float_precision.value_or(defaults->float_precision); + auto fmt_str = fmt::format("{{:>{sign:s}{width}.{prec}e}}", fmt::arg("sign", sign), + fmt::arg("width", width_), fmt::arg("prec", prec)); + return fmt::format(fmt::runtime(fmt_str), val); + } + auto empty_cell = empty_cell_val.value_or(defaults->empty_cell_val); + return fmt::format("{:>{width}s}", empty_cell, fmt::arg("width", width_)); + } + Column(std::string name, std::string header_text = "", + std::optional min_left_padding = {}, + std::optional float_precision = {}, + std::optional empty_cell_val = {}, + std::optional fmt_sign = {}) + : name(std::move(name)), header_text(std::move(header_text)), + min_left_padding(min_left_padding), float_precision(float_precision), + empty_cell_val(std::move(empty_cell_val)), fmt_sign(std::move(fmt_sign)) + { + } + + std::vector data = {}; + std::string header_text = ""; + + std::optional min_left_padding = {}; + std::optional float_precision = {}; + std::optional empty_cell_val = {}; + std::optional fmt_sign = {}; + + [[nodiscard]] size_t n_rows() const { return data.size(); } + + // Convenience operator at higher level + auto operator<<(double val) { return data.emplace_back(val); } +}; + +class Table +{ + // Column-wise mini-table for storing data and and printing to csv file for doubles. + // Future: allow int and other output, allow non-owning memeory via span + std::vector cols = {}; + + // Cache value to reserve vector space by default + size_t reserve_n_rows = 0; + +public: + // Default column options; can be overwritten column-wise + ColumnOptions col_options = {}; + + // Global printing options + std::string print_col_separator = ","; + std::string print_row_separator = "\n"; + + // Table properties + + [[nodiscard]] size_t n_cols() const { return cols.size(); } + [[nodiscard]] size_t n_rows() const + { + if (n_cols() == 0) + { + return 0; + } + auto max_col = + std::max_element(cols.begin(), cols.end(), [](const auto &a, const auto &b) + { return a.n_rows() < b.n_rows(); }); + return max_col->n_rows(); + } + + void reserve(size_t n_rows, size_t n_cols) + { + reserve_n_rows = n_rows; + cols.reserve(n_cols); + for (auto &col : cols) + { + col.data.reserve(n_rows); + } + } + + // Insert columns: map like interface + + bool insert_column(std::string column_name, std::string column_header = "") + { + auto it = std::find_if(cols.begin(), cols.end(), + [&column_name](auto &c) { return c.name == column_name; }); + if (it != cols.end()) + { + return false; + } + auto &col = cols.emplace_back(std::move(column_name), std::move(column_header)); + col.defaults = &col_options; + if (reserve_n_rows > 0) + { + col.data.reserve(reserve_n_rows); + } + return true; + } + bool insert_column(Column &&column) + { + auto it = std::find_if(cols.begin(), cols.end(), + [&column](auto &c) { return c.name == column.name; }); + if (it != cols.end()) + { + return false; + } + auto &col = cols.emplace_back(std::move(column)); + col.defaults = &col_options; + if (reserve_n_rows > 0) + { + col.data.reserve(reserve_n_rows); + } + return true; + } + + // Access columns via vector position or column name + + Column &operator[](size_t idx) { return cols.at(idx); } + const Column &operator[](size_t idx) const { return (*this)[idx]; } + + Column &operator[](std::string_view name) + { + auto it = + std::find_if(cols.begin(), cols.end(), [&name](auto &c) { return c.name == name; }); + if (it == cols.end()) + { + throw std::out_of_range(fmt::format("Column {} not found in table", name).c_str()); + } + return *it; + } + const Column &operator[](std::string_view name) const { return (*this)[name]; } + + auto begin() { return cols.begin(); } + auto end() { return cols.end(); } + auto cbegin() const { return cols.begin(); } + auto cend() const { return cols.end(); } + + // Formatting and Printing Options + // TODO: Improve all the functions below with ranges in C++20 + +public: + template + void append_header(T &buf) const + { + auto to = [&buf](auto f, auto &&...a) + { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; + + for (int i = 0; i < n_cols(); i++) + { + if (i > 0) + { + to("{:s}", print_col_separator); + } + to("{:s}", cols[i].format_header()); + } + to("{:s}", print_row_separator); + } + + template + void append_row(T &buf, size_t row_j) const + { + auto to = [&buf](auto f, auto &&...a) + { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; + + for (int i = 0; i < n_cols(); i++) + { + if (i > 0) + { + to("{:s}", print_col_separator); + } + to("{:s}", cols[i].format_row(row_j)); + } + to("{:s}", print_row_separator); + } + + [[nodiscard]] std::string format_header() const + { + fmt::memory_buffer buf{}; + append_header(buf); + return {buf.data(), buf.size()}; + } + + [[nodiscard]] std::string format_row(size_t j) const + { + fmt::memory_buffer buf{}; + append_row(buf, j); + return {buf.data(), buf.size()}; + } + + [[nodiscard]] std::string format_table() const + { + fmt::memory_buffer buf{}; + append_header(buf); + for (int j = 0; j < n_rows(); j++) + { + append_row(buf, j); + } + return {buf.data(), buf.size()}; + } +}; + +// Wrapper for storing Table to csv file wish row wise updates + +class TableWithCSVFile +{ + std::string csv_file_fullpath_ = ""; + unsigned long file_append_curser = -1; + +public: + Table table = {}; + + TableWithCSVFile() = default; + explicit TableWithCSVFile(std::string csv_file_fullpath) + : csv_file_fullpath_{std::move(csv_file_fullpath)} + { + // Validate + auto file_buf = fmt::output_file( + csv_file_fullpath_, fmt::file::WRONLY | fmt::file::CREATE | fmt::file::TRUNC); + } + + void WriteFullTableTrunc() + { + auto file_buf = fmt::output_file( + csv_file_fullpath_, fmt::file::WRONLY | fmt::file::CREATE | fmt::file::TRUNC); + file_buf.print("{}", table.format_table()); + file_append_curser = table.n_rows(); + } + + void AppendHeader() + { + if (file_append_curser != -1) + { + // ReplaceHeader; + return; + } + auto file_buf = + fmt::output_file(csv_file_fullpath_, fmt::file::WRONLY | fmt::file::APPEND); + file_buf.print("{}", table.format_header()); + file_append_curser++; + } + void AppendRow() + { + if (file_append_curser < 0) + { + // ReplaceHeader; + return; + } + auto file_buf = + fmt::output_file(csv_file_fullpath_, fmt::file::WRONLY | fmt::file::APPEND); + file_buf.print("{}", table.format_row(file_append_curser)); + file_append_curser++; + } + + [[nodiscard]] auto GetAppendRowCurser() const { return file_append_curser; } +}; + +} // namespace palace + +#endif // PALACE_UTILS_TABLECSV_HPP diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index df836c940..e745236df 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -17,6 +17,7 @@ FetchContent_MakeAvailable(Catch2) add_executable(unit-tests ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test-libceed.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test-tablecsv.cpp ) target_link_libraries(unit-tests PRIVATE ${LIB_TARGET_NAME} Catch2::Catch2) diff --git a/test/unit/test-tablecsv.cpp b/test/unit/test-tablecsv.cpp new file mode 100644 index 000000000..44846e02d --- /dev/null +++ b/test/unit/test-tablecsv.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include "utils/tablecsv.hpp" + +using namespace palace; + +TEST_CASE("TableCSV", "[tablecsv]") +{ + Table table{}; + table.reserve(5, 2); + + // Quick defaults + CHECK(table.print_col_separator == ","); + CHECK(table.print_row_separator == "\n"); + + REQUIRE(table.n_rows() == 0); + REQUIRE(table.n_cols() == 0); + + // Add and access columns + { + auto status_1 = table.insert_column("col_1"); + REQUIRE(status_1); + auto &col_1 = table["col_1"]; + CHECK(col_1.header_text == ""); + + col_1.header_text = "Header Col 1"; + auto &col_1i = table[0]; + CHECK(col_1i.header_text == "Header Col 1"); + + auto status_2 = table.insert_column("col_2", "Header Col 2"); + REQUIRE(status_2); + + auto &col_2 = table["col_2"]; + CHECK(col_2.header_text == "Header Col 2"); + col_2.data.push_back(2.0); + + CHECK(col_1.data.capacity() == 5); + CHECK(col_2.data.capacity() == 5); + } + + REQUIRE(table.n_rows() == 1); + REQUIRE(table.n_cols() == 2); + + // Invalid access + CHECK_THROWS(table["col3"]); + CHECK_THROWS(table[2]); + + // Check reserved: invalidates references + { + table.reserve(6, 3); + CHECK(table["col_1"].data.capacity() == 6); + CHECK(table["col_2"].data.capacity() == 6); + } + + { + table.insert_column(Column("col_3", "Header Col 3")); + auto &col_3 = table["col_3"]; + CHECK(col_3.header_text == "Header Col 3"); + col_3.data.push_back(3.0); + col_3 << 6.0; + } + + std::vector cols_n_row; + std::transform(table.cbegin(), table.cend(), std::back_inserter(cols_n_row), + [](auto &c) { return c.n_rows(); }); + + CHECK(cols_n_row == std::vector{0, 1, 2}); + + CHECK(table.n_cols() == 3); + CHECK(table.n_rows() == 2); + + // clang-format off + auto table_str1 = std::string( + " Header Col 1, Header Col 2, Header Col 3\n" + " NULL, +2.000000000e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" + ); + // clang-format on + CHECK(table.format_table() == table_str1); + + auto &col_2 = table["col_2"]; + col_2.min_left_padding = 5; + col_2.float_precision = 6; + + // clang-format off + auto table_str2 = std::string( + " Header Col 1, Header Col 2, Header Col 3\n" + " NULL, +2.000000e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" + ); + // clang-format on + CHECK(table.format_table() == table_str2); + + table["col_2"].fmt_sign = " "; + + col_2.min_left_padding = 0; + col_2.float_precision = 2; + + // clang-format off + auto table_str3 = std::string( + " Header Col 1,Header Col 2, Header Col 3\n" + " NULL, 2.00e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" + ); + // clang-format on + CHECK(table.format_table() == table_str3); + + col_2.fmt_sign.reset(); + col_2.min_left_padding.reset(); + col_2.float_precision.reset(); + CHECK(table.format_table() == table_str1); + + // REQUIRE_NOTHROW(boundary_ex_bool.SetUp(*config.find("boundaries_1_pass"))); +} \ No newline at end of file From 41446fa9f8ffdab21c2f3cc8641ee60febe81695 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:48 -0800 Subject: [PATCH 05/21] Add vdim to interpolation operator --- palace/fem/interpolator.cpp | 32 ++++++++++++++++++++++++++++++-- palace/fem/interpolator.hpp | 6 +++++- palace/models/postoperator.cpp | 6 +++--- palace/models/postoperator.hpp | 1 + 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/palace/fem/interpolator.cpp b/palace/fem/interpolator.cpp index 50b017882..01e3468f3 100644 --- a/palace/fem/interpolator.cpp +++ b/palace/fem/interpolator.cpp @@ -4,6 +4,7 @@ #include "interpolator.hpp" #include +#include "fem/fespace.hpp" #include "fem/gridfunction.hpp" #include "utils/communication.hpp" #include "utils/iodata.hpp" @@ -19,14 +20,41 @@ constexpr auto GSLIB_NEWTON_TOL = 1.0e-12; } // namespace +namespace +{ +int MFEM_GridFunction_VectorDim(const mfem::FiniteElementSpace *fes) +{ + using namespace mfem; + const FiniteElement *fe; + if (!fes->GetNE()) + { + static const Geometry::Type geoms[3] = {Geometry::SEGMENT, Geometry::TRIANGLE, + Geometry::TETRAHEDRON}; + fe = fes->FEColl()->FiniteElementForGeometry(geoms[fes->GetMesh()->Dimension() - 1]); + } + else + { + fe = fes->GetFE(0); + } + if (!fe || fe->GetRangeType() == FiniteElement::SCALAR) + { + return fes->GetVDim(); + } + return fes->GetVDim() * std::max(fes->GetMesh()->SpaceDimension(), fe->GetRangeDim()); +} +} // namespace + #if defined(MFEM_USE_GSLIB) -InterpolationOperator::InterpolationOperator(const IoData &iodata, mfem::ParMesh &mesh) - : op(mesh.GetComm()) +InterpolationOperator::InterpolationOperator(const IoData &iodata, + FiniteElementSpace &nd_space) + : op(nd_space.GetParMesh().GetComm()), + v_dim_fes(MFEM_GridFunction_VectorDim(&nd_space.Get())) #else InterpolationOperator::InterpolationOperator(const IoData &iodata, mfem::ParMesh &mesh) #endif { #if defined(MFEM_USE_GSLIB) + auto &mesh = nd_space.GetParMesh(); // Set up probes interpolation. All processes search for all points. if (iodata.domains.postpro.probe.empty()) { diff --git a/palace/fem/interpolator.hpp b/palace/fem/interpolator.hpp index 468512f20..292c8cafe 100644 --- a/palace/fem/interpolator.hpp +++ b/palace/fem/interpolator.hpp @@ -13,6 +13,7 @@ namespace palace class GridFunction; class IoData; +class FiniteElementSpace; // // A class which wraps MFEM's GSLIB interface for high-order field interpolation. @@ -25,11 +26,14 @@ class InterpolationOperator #endif std::vector op_idx; + int v_dim_fes; // dimension of interpolated vector from NDSpace + std::vector ProbeField(const mfem::ParGridFunction &U); public: - InterpolationOperator(const IoData &iodata, mfem::ParMesh &mesh); + InterpolationOperator(const IoData &iodata, FiniteElementSpace &nd_space); + auto GetVDim() const { return v_dim_fes; } const auto &GetProbes() const { return op_idx; } std::vector> ProbeField(const GridFunction &U); diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 2a584b6ba..55a45bc75 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -52,7 +52,7 @@ PostOperator::PostOperator(const IoData &iodata, SpaceOperator &space_op, paraview(CreateParaviewPath(iodata, name), &space_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", &space_op.GetNDSpace().GetParMesh()), - interp_op(iodata, space_op.GetNDSpace().GetParMesh()) + interp_op(iodata, space_op.GetNDSpace()) { U_e = std::make_unique>(*E, mat_op); U_m = std::make_unique>(*B, mat_op); @@ -95,7 +95,7 @@ PostOperator::PostOperator(const IoData &iodata, LaplaceOperator &laplace_op, paraview(CreateParaviewPath(iodata, name), &laplace_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", &laplace_op.GetNDSpace().GetParMesh()), - interp_op(iodata, laplace_op.GetNDSpace().GetParMesh()) + interp_op(iodata, laplace_op.GetNDSpace()) { // Note: When using this constructor, you should not use any of the magnetic field related // postprocessing functions (magnetic field energy, inductor energy, surface currents, @@ -122,7 +122,7 @@ PostOperator::PostOperator(const IoData &iodata, CurlCurlOperator &curlcurl_op, paraview(CreateParaviewPath(iodata, name), &curlcurl_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", &curlcurl_op.GetNDSpace().GetParMesh()), - interp_op(iodata, curlcurl_op.GetNDSpace().GetParMesh()) + interp_op(iodata, curlcurl_op.GetNDSpace()) { // Note: When using this constructor, you should not use any of the electric field related // postprocessing functions (electric field energy, capacitor energy, surface charge, diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index 2522d815e..b21e79fb0 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -191,6 +191,7 @@ class PostOperator // the internal grid functions are real-valued, the returned fields have only nonzero real // parts. Output vectors are ordered by vector dimension, that is [v1x, v1y, v1z, v2x, // v2y, v2z, ...]. + int GetInterpolationOpVDim() const { return interp_op.GetVDim(); } const auto &GetProbes() const { return interp_op.GetProbes(); } std::vector> ProbeEField() const; std::vector> ProbeBField() const; From dc6516b858c8a1e662f7fe696764784d0c9669c5 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:48 -0800 Subject: [PATCH 06/21] First pass at new postpro printers - switch post-processing functions to small classes that store data in tables - replace sequential row printing - separate write during sweep step and final write more clearly --- palace/drivers/basesolver.cpp | 762 ++++++++++++++----------- palace/drivers/basesolver.hpp | 112 ++-- palace/drivers/drivensolver.cpp | 568 +++++++++--------- palace/drivers/drivensolver.hpp | 100 +++- palace/drivers/eigensolver.cpp | 603 +++++++++++-------- palace/drivers/eigensolver.hpp | 94 ++- palace/drivers/electrostaticsolver.cpp | 136 +++-- palace/drivers/electrostaticsolver.hpp | 19 +- palace/drivers/magnetostaticsolver.cpp | 136 +++-- palace/drivers/magnetostaticsolver.hpp | 19 +- palace/drivers/transientsolver.cpp | 360 +++++------- palace/drivers/transientsolver.hpp | 59 +- palace/models/postoperator.cpp | 9 + 13 files changed, 1679 insertions(+), 1298 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index 18920c04d..9cbd54af7 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -15,6 +15,7 @@ #include "linalg/ksp.hpp" #include "models/domainpostoperator.hpp" #include "models/postoperator.hpp" +#include "models/spaceoperator.hpp" #include "models/surfacepostoperator.hpp" #include "utils/communication.hpp" #include "utils/dorfler.hpp" @@ -109,7 +110,7 @@ mfem::Array MarkedElements(const Vector &e, double threshold) BaseSolver::BaseSolver(const IoData &iodata, bool root, int size, int num_thread, const char *git_tag) - : iodata(iodata), post_dir(GetPostDir(iodata.problem.output)), root(root), table(8, 9, 9) + : iodata(iodata), post_dir(GetPostDir(iodata.problem.output)), root(root) { // Create directory for output. if (root && !std::filesystem::exists(post_dir)) @@ -347,452 +348,515 @@ struct ProbeData } // namespace -void BaseSolver::PostprocessDomains(const PostOperator &post_op, const std::string &name, - int step, double time, double E_elec, double E_mag, - double E_cap, double E_ind) const +BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const DomainPostOperator &dom_post_op, + const std::string &idx_col_name, + int n_expected_rows) + : do_measurement_{do_measurement}, root_{root} { - // If domains have been specified for postprocessing, compute the corresponding values - // and write out to disk. - if (post_dir.length() == 0) + do_measurement_ = do_measurement_ // + && post_dir.length() > 0; // Valid output dir + + if (!do_measurement_ || !root_) { return; } + using fmt::format; - // Write the field and lumped element energies. + domain_E = TableWithCSVFile(post_dir + "domain-E.csv"); + domain_E.table.reserve(n_expected_rows, 4 + dom_post_op.M_i.size()); + domain_E.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); + + domain_E.table.insert_column("Ee", "E_elec (J)"); + domain_E.table.insert_column("Em", "E_mag (J)"); + domain_E.table.insert_column("Ec", "E_cap (J)"); + domain_E.table.insert_column("Ei", "E_ind (J)"); + + for (const auto &[idx, data] : dom_post_op.M_i) + { + domain_E.table.insert_column(format("Ee{}", idx), format("E_elec[{}] (J)", idx)); + domain_E.table.insert_column(format("pe{}", idx), format("p_elec[{}]", idx)); + domain_E.table.insert_column(format("Em{}", idx), format("E_mag[{}] (J)", idx)); + domain_E.table.insert_column(format("pm{}", idx), format("p_mag[{}]", idx)); + } + domain_E.AppendHeader(); +} + +void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionful, + const PostOperator &post_op, + double E_elec, double E_mag, + double E_cap, double E_ind, + const IoData &iodata) +{ + if (!do_measurement_) + { + return; + } + + // MPI Gather std::vector energy_data; energy_data.reserve(post_op.GetDomainPostOp().M_i.size()); for (const auto &[idx, data] : post_op.GetDomainPostOp().M_i) { - const double E_elec_i = (E_elec > 0.0) ? post_op.GetEFieldEnergy(idx) : 0.0; - const double E_mag_i = (E_mag > 0.0) ? post_op.GetHFieldEnergy(idx) : 0.0; - energy_data.push_back({idx, E_elec_i, E_mag_i}); + double E_elec_i = (E_elec > 0.0) ? post_op.GetEFieldEnergy(idx) : 0.0; + double E_mag_i = (E_mag > 0.0) ? post_op.GetHFieldEnergy(idx) : 0.0; + energy_data.emplace_back(EnergyData{idx, E_elec_i, E_mag_i}); } - if (root) + + if (!root_) + { + return; + } + + using VT = IoData::ValueType; + using fmt::format; + + domain_E.table["idx"] << idx_value_dimensionful; + + domain_E.table["Ee"] << iodata.DimensionalizeValue(VT::ENERGY, E_elec); + domain_E.table["Em"] << iodata.DimensionalizeValue(VT::ENERGY, E_mag); + domain_E.table["Ec"] << iodata.DimensionalizeValue(VT::ENERGY, E_cap); + domain_E.table["Ei"] << iodata.DimensionalizeValue(VT::ENERGY, E_ind); + + // Write the field and lumped element energies. + for (const auto &[idx, E_e, E_m] : energy_data) { - std::string path = post_dir + "domain-E.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) + domain_E.table[format("Ee{}", idx)] << iodata.DimensionalizeValue(VT::ENERGY, E_e); + domain_E.table[format("pe{}", idx)] + << ((std::abs(E_elec) > 0.0) ? (E_e / E_elec) : 0.0); + domain_E.table[format("Em{}", idx)] << iodata.DimensionalizeValue(VT::ENERGY, E_m); + domain_E.table[format("pm{}", idx)] << ((std::abs(E_mag) > 0.0) ? E_m / E_mag : 0.0); + } + + domain_E.WriteFullTableTrunc(); +} + +BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const PostOperator &post_op, + const std::string &idx_col_name, + int n_expected_rows) + : root_{root}, + do_measurement_flux_(do_measurement // + && post_dir.length() > 0 // Valid output dir + && post_op.GetSurfacePostOp().flux_surfs.size() > 0 // Has flux + ), + do_measurement_eps_(do_measurement // + && post_dir.length() > 0 // Valid output dir + && post_op.GetSurfacePostOp().eps_surfs.size() > 0 // Has eps + ) +{ + if (!root_) + { + return; + } + using fmt::format; + + if (do_measurement_flux_) + { + surface_F = TableWithCSVFile(post_dir + "surface-F.csv"); + surface_F.table.reserve(n_expected_rows, + 2 * post_op.GetSurfacePostOp().flux_surfs.size() + 1); + surface_F.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); + + bool has_imaginary = post_op.HasImag(); + for (const auto &[idx, data] : post_op.GetSurfacePostOp().flux_surfs) { - // clang-format off - output.print("{:>{}s},{:>{}s},{:>{}s},{:>{}s},{:>{}s}{}", - name, table.w1, - "E_elec (J)", table.w, - "E_mag (J)", table.w, - "E_cap (J)", table.w, - "E_ind (J)", table.w, - energy_data.empty() ? "" : ","); - // clang-format on - for (const auto &data : energy_data) + switch (data.type) { - // clang-format off - output.print("{:>{}s},{:>{}s},{:>{}s},{:>{}s}{}", - "E_elec[" + std::to_string(data.idx) + "] (J)", table.w, - "p_elec[" + std::to_string(data.idx) + "]", table.w, - "E_mag[" + std::to_string(data.idx) + "] (J)", table.w, - "p_mag[" + std::to_string(data.idx) + "]", table.w, - (data.idx == energy_data.back().idx) ? "" : ","); - // clang-format on + case SurfaceFluxType::ELECTRIC: + if (has_imaginary) + { + surface_F.table.insert_column(format("F_{}_re", idx), + format("Re{{Φ_elec[{}]}} (C)", idx)); + surface_F.table.insert_column(format("F_{}_im", idx), + format("Im{{Φ_elec[{}]}} (C)", idx)); + } + else + { + surface_F.table.insert_column(format("F_{}_re", idx), + format("Φ_elec[{}] (C)", idx)); + } + break; + case SurfaceFluxType::MAGNETIC: + if (has_imaginary) + { + surface_F.table.insert_column(format("F_{}_re", idx), + format("Re{{Φ_mag[{}]}} (Wb)", idx)); + surface_F.table.insert_column(format("F_{}_im", idx), + format("Im{{Φ_mag[{}]}} (Wb)", idx)); + } + else + { + surface_F.table.insert_column(format("F_{}_re", idx), + format("Φ_mag[{}] (Wb)", idx)); + } + break; + case SurfaceFluxType::POWER: + surface_F.table.insert_column(format("F_{}_re", idx), + format("Φ_pow[{}] (W)", idx)); + break; } - output.print("\n"); } - // clang-format off - output.print("{:{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e}{}", - time, table.w1, table.p1, - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, E_elec), - table.w, table.p, - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, E_mag), - table.w, table.p, - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, E_cap), - table.w, table.p, - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, E_ind), - table.w, table.p, - energy_data.empty() ? "" : ","); - // clang-format on - for (const auto &data : energy_data) + surface_F.AppendHeader(); + } + + if (do_measurement_eps_) + { + surface_Q = TableWithCSVFile(post_dir + "surface-Q.csv"); + surface_Q.table.reserve(n_expected_rows, + 2 * post_op.GetSurfacePostOp().eps_surfs.size() + 1); + surface_Q.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); + + for (const auto &[idx, data] : post_op.GetSurfacePostOp().eps_surfs) { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e}{}", - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, data.E_elec), - table.w, table.p, - (std::abs(E_elec) > 0.0) ? data.E_elec / E_elec : 0.0, table.w, table.p, - iodata.DimensionalizeValue(IoData::ValueType::ENERGY, data.E_mag), - table.w, table.p, - (std::abs(E_mag) > 0.0) ? data.E_mag / E_mag : 0.0, table.w, table.p, - (data.idx == energy_data.back().idx) ? "" : ","); - // clang-format on + surface_Q.table.insert_column(format("p_{}", idx), format("p_surf[{}]", idx)); + surface_Q.table.insert_column(format("Q_{}", idx), format("Q_surf[{}]", idx)); } - output.print("\n"); } } -void BaseSolver::PostprocessSurfaces(const PostOperator &post_op, const std::string &name, - int step, double time, double E_elec, - double E_mag) const +void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimensionful, + const PostOperator &post_op, + double E_elec, double E_mag, + const IoData &iodata) { - // If surfaces have been specified for postprocessing, compute the corresponding values - // and write out to disk. The passed in E_elec is the sum of the E-field and lumped - // capacitor energies, and E_mag is the same for the B-field and lumped inductors. - if (post_dir.length() == 0) + if (!do_measurement_flux_) { return; } + using VT = IoData::ValueType; + using fmt::format; - // Write the integrated surface flux. + // Gather const bool has_imaginary = post_op.HasImag(); std::vector flux_data; flux_data.reserve(post_op.GetSurfacePostOp().flux_surfs.size()); for (const auto &[idx, data] : post_op.GetSurfacePostOp().flux_surfs) { - const std::complex Phi = post_op.GetSurfaceFlux(idx); - double scale = 1.0; + auto Phi = post_op.GetSurfaceFlux(idx); switch (data.type) { case SurfaceFluxType::ELECTRIC: - scale = iodata.DimensionalizeValue(IoData::ValueType::CAPACITANCE, 1.0); - scale *= iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, 1.0); + Phi *= iodata.DimensionalizeValue(VT::CAPACITANCE, 1.0); + Phi *= iodata.DimensionalizeValue(VT::VOLTAGE, 1.0); break; case SurfaceFluxType::MAGNETIC: - scale = iodata.DimensionalizeValue(IoData::ValueType::INDUCTANCE, 1.0); - scale *= iodata.DimensionalizeValue(IoData::ValueType::CURRENT, 1.0); + Phi *= iodata.DimensionalizeValue(VT::INDUCTANCE, 1.0); + Phi *= iodata.DimensionalizeValue(VT::CURRENT, 1.0); break; case SurfaceFluxType::POWER: - scale = iodata.DimensionalizeValue(IoData::ValueType::POWER, 1.0); + Phi *= iodata.DimensionalizeValue(VT::POWER, 1.0); break; } - flux_data.push_back({idx, Phi * scale, data.type}); + flux_data.emplace_back(FluxData{idx, Phi, data.type}); } - if (root && !flux_data.empty()) + + if (!root_) + { + return; + } + + surface_F.table["idx"] << idx_value_dimensionful; + + for (const auto &[idx, Phi, data_type] : flux_data) { - std::string path = post_dir + "surface-F.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) + surface_F.table[format("F_{}_re", idx)] << Phi.real(); + if (has_imaginary && + (data_type == SurfaceFluxType::ELECTRIC || data_type == SurfaceFluxType::MAGNETIC)) { - output.print("{:>{}s},", name, table.w1); - for (const auto &data : flux_data) - { - std::string name, unit; - switch (data.type) - { - case SurfaceFluxType::ELECTRIC: - name = "Φ_elec"; - unit = "(C)"; - break; - case SurfaceFluxType::MAGNETIC: - name = "Φ_mag"; - unit = "(Wb)"; - break; - case SurfaceFluxType::POWER: - name = "Φ_pow"; - unit = "(W)"; - break; - } - if (has_imaginary && data.type != SurfaceFluxType::POWER) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Re{" + name + "[" + std::to_string(data.idx) + "]} " + unit, table.w, - "Im{" + name + "[" + std::to_string(data.idx) + "]} " + unit, table.w, - (data.idx == flux_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{:>{}s}{}", - name + "[" + std::to_string(data.idx) + "] " + unit, table.w, - (data.idx == flux_data.back().idx) ? "" : ","); - // clang-format on - } - } - output.print("\n"); + surface_F.table[format("F_{}_im", idx)] << Phi.imag(); } - output.print("{:{}.{}e},", time, table.w1, table.p1); - for (const auto &data : flux_data) - { - if (has_imaginary && data.type != SurfaceFluxType::POWER) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.Phi.real(), table.w, table.p, - data.Phi.imag(), table.w, table.p, - (data.idx == flux_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{:+{}.{}e}{}", - data.Phi.real(), table.w, table.p, - (data.idx == flux_data.back().idx) ? "" : ","); - // clang-format on - } - } - output.print("\n"); } + surface_F.WriteFullTableTrunc(); +} + +void BaseSolver::SurfacesPostPrinter::AddMeasurementEps(double idx_value_dimensionful, + const PostOperator &post_op, + double E_elec, double E_mag, + const IoData &iodata) +{ + if (!do_measurement_eps_) + { + return; + } + using VT = IoData::ValueType; + using fmt::format; - // Write the Q-factors due to interface dielectric loss. + // Gather std::vector eps_data; eps_data.reserve(post_op.GetSurfacePostOp().eps_surfs.size()); for (const auto &[idx, data] : post_op.GetSurfacePostOp().eps_surfs) { - const double p = post_op.GetInterfaceParticipation(idx, E_elec); - const double tandelta = post_op.GetSurfacePostOp().GetInterfaceLossTangent(idx); - const double Q = - (p == 0.0 || tandelta == 0.0) ? mfem::infinity() : 1.0 / (tandelta * p); - eps_data.push_back({idx, p, Q}); + double p = post_op.GetInterfaceParticipation(idx, E_elec); + double tandelta = post_op.GetSurfacePostOp().GetInterfaceLossTangent(idx); + double Q = (p == 0.0 || tandelta == 0.0) ? mfem::infinity() : 1.0 / (tandelta * p); + eps_data.emplace_back(EpsData{idx, p, Q}); } - if (root && !eps_data.empty()) + + if (!root_) { - std::string path = post_dir + "surface-Q.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", name, table.w1); - for (const auto &data : eps_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "p_surf[" + std::to_string(data.idx) + "]", table.w, - "Q_surf[" + std::to_string(data.idx) + "]", table.w, - (data.idx == eps_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - output.print("{:{}.{}e},", time, table.w1, table.p1); - for (const auto &data : eps_data) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.p, table.w, table.p, - data.Q, table.w, table.p, - (data.idx == eps_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + return; } + + surface_Q.table["idx"] << idx_value_dimensionful; + for (const auto &[idx, p, Q] : eps_data) + { + surface_Q.table[format("p_{}", idx)] << p; + surface_Q.table[format("Q_{}", idx)] << Q; + } + surface_Q.WriteFullTableTrunc(); } -void BaseSolver::PostprocessProbes(const PostOperator &post_op, const std::string &name, - int step, double time) const +void BaseSolver::SurfacesPostPrinter::AddMeasurement(double idx_value_dimensionful, + const PostOperator &post_op, + double E_elec, double E_mag, + const IoData &iodata) +{ + // If surfaces have been specified for postprocessing, compute the corresponding values + // and write out to disk. The passed in E_elec is the sum of the E-field and lumped + // capacitor energies, and E_mag is the same for the B-field and lumped inductors. + AddMeasurementFlux(idx_value_dimensionful, post_op, E_elec, E_mag, iodata); + AddMeasurementEps(idx_value_dimensionful, post_op, E_elec, E_mag, iodata); +} + +BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const PostOperator &post_op, + const std::string &idx_col_name, + int n_expected_rows) + : root_{root}, do_measurement_E_{do_measurement}, do_measurement_B_{do_measurement}, + has_imag{post_op.HasImag()}, v_dim{post_op.GetInterpolationOpVDim()} { #if defined(MFEM_USE_GSLIB) - // If probe locations have been specified for postprocessing, compute the corresponding - // field values and write out to disk. - if (post_dir.length() == 0) - { - return; - } + do_measurement_E_ = do_measurement_E_ // + && (post_dir.length() > 0) // Valid output dir + && (post_op.GetProbes().size() > 0) // Has probes defined + && post_op.HasE(); // Has E fields + + do_measurement_B_ = do_measurement_B_ // + && (post_dir.length() > 0) // Valid output dir + && (post_op.GetProbes().size() > 0) // Has probes defined + && post_op.HasB(); // Has B fields - // Write the computed field values at probe locations. - if (post_op.GetProbes().size() == 0) + if (!root_ || (!do_measurement_E_ && !do_measurement_B_)) { return; } - const bool has_imaginary = post_op.HasImag(); - for (int f = 0; f < 2; f++) + using fmt::format; + int scale_col = (has_imag ? 2 : 1) * v_dim; + auto dim_labeler = [](int i) -> std::string { - // Probe data is ordered as [Fx1, Fy1, Fz1, Fx2, Fy2, Fz2, ...]. - if (f == 0 && !post_op.HasE()) + switch (i) { - continue; + // Note: Zero-based indexing here + case 0: + return "x"; + case 1: + return "y"; + case 2: + return "z"; + default: + return format("d{}", i); } - if (f == 1 && !post_op.HasB()) - { - continue; - } - const std::string F = (f == 0) ? "E" : "B"; - const std::string unit = (f == 0) ? "(V/m)" : "(Wb/m²)"; - const auto type = (f == 0) ? IoData::ValueType::FIELD_E : IoData::ValueType::FIELD_B; - const auto vF = (f == 0) ? post_op.ProbeEField() : post_op.ProbeBField(); - const int dim = vF.size() / post_op.GetProbes().size(); - std::vector probe_data; - probe_data.reserve(post_op.GetProbes().size()); - int i = 0; + }; + + if (do_measurement_E_) + { + probe_E = TableWithCSVFile(post_dir + "probe-E.csv"); + probe_E.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); + probe_E.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); + for (const auto &idx : post_op.GetProbes()) { - probe_data.push_back( - {idx, iodata.DimensionalizeValue(type, vF[i * dim]), - iodata.DimensionalizeValue(type, vF[i * dim + 1]), - (dim == 3) ? iodata.DimensionalizeValue(type, vF[i * dim + 2]) : 0.0}); - i++; - } - if (root && !probe_data.empty()) - { - std::string path = post_dir + "probe-" + F + ".csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) + for (int i_dim = 0; i_dim < v_dim; i_dim++) { - output.print("{:>{}s},", name, table.w1); - if (has_imaginary) + if (has_imag) { - for (const auto &data : probe_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s},{:>{}s},{:>{}s}", - "Re{" + F + "_x[" + std::to_string(data.idx) + "]} " + unit, table.w, - "Im{" + F + "_x[" + std::to_string(data.idx) + "]} " + unit, table.w, - "Re{" + F + "_y[" + std::to_string(data.idx) + "]} " + unit, table.w, - "Im{" + F + "_y[" + std::to_string(data.idx) + "]} " + unit, table.w); - // clang-format on - if (dim == 3) - { - // clang-format off - output.print(",{:>{}s},{:>{}s}{}", - "Re{" + F + "_z[" + std::to_string(data.idx) + "]} " + unit, table.w, - "Im{" + F + "_z[" + std::to_string(data.idx) + "]} " + unit, table.w, - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{}", - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - } + probe_E.table.insert_column( + format("E{}_{}_re", idx, i_dim), + format("Re{{E_{}[{}]}} (V/m)", dim_labeler(i_dim), idx)); + probe_E.table.insert_column( + format("E{}_{}_im", idx, i_dim), + format("Im{{E_{}[{}]}} (V/m)", dim_labeler(i_dim), idx)); } else { - for (const auto &data : probe_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}", - F + "_x[" + std::to_string(data.idx) + "] " + unit, table.w, - F + "_y[" + std::to_string(data.idx) + "] " + unit, table.w); - // clang-format on - if (dim == 3) - { - // clang-format off - output.print(",{:>{}s}{}", - F + "_z[" + std::to_string(data.idx) + "] " + unit, table.w, - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{}", - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - } + probe_E.table.insert_column(format("E{}_{}_re", idx, i_dim), + format("E_{}[{}] (V/m)", dim_labeler(i_dim), idx)); } - output.print("\n"); } - output.print("{:{}.{}e},", time, table.w1, table.p1); - if (has_imaginary) + } + probe_E.AppendHeader(); + } + + if (do_measurement_B_) + { + probe_B = TableWithCSVFile(post_dir + "probe-B.csv"); + probe_B.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); + probe_B.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); + + for (const auto &idx : post_op.GetProbes()) + { + for (int i_dim = 0; i_dim < v_dim; i_dim++) { - for (const auto &data : probe_data) + if (has_imag) { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e}", - data.Fx.real(), table.w, table.p, - data.Fx.imag(), table.w, table.p, - data.Fy.real(), table.w, table.p, - data.Fy.imag(), table.w, table.p); - // clang-format on - if (dim == 3) - { - // clang-format off - output.print(",{:+{}.{}e},{:+{}.{}e}{}", - data.Fz.real(), table.w, table.p, - data.Fz.imag(), table.w, table.p, - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{}", - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } + probe_B.table.insert_column( + format("B{}_{}_re", idx, i_dim), + format("Re{{B_{}[{}]}} (Wb/m²)", dim_labeler(i_dim), idx)); + probe_B.table.insert_column( + format("B{}_{}_im", idx, i_dim), + format("Im{{B_{}[{}]}} (Wb/m²)", dim_labeler(i_dim), idx)); } - } - else - { - for (const auto &data : probe_data) + else { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}", - data.Fx.real(), table.w, table.p, - data.Fy.real(), table.w, table.p); - // clang-format on - if (dim == 3) - { - // clang-format off - output.print(",{:+{}.{}e}{}", - data.Fz.real(), table.w, table.p, - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } - else - { - // clang-format off - output.print("{}", - (data.idx == probe_data.back().idx) ? "" : ","); - // clang-format on - } + probe_B.table.insert_column(format("B{}_{}_re", idx, i_dim), + format("B_{}[{}] (Wb/m²)", dim_labeler(i_dim), idx)); } } - output.print("\n"); } + probe_B.AppendHeader(); } #endif } -void BaseSolver::PostprocessFields(const PostOperator &post_op, int step, double time) const +void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful, + const PostOperator &post_op, + const IoData &iodata) { - // Save the computed fields in parallel in format for viewing with ParaView. - BlockTimer bt(Timer::IO); - if (post_dir.length() == 0) + if (!do_measurement_E_) { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk!\n"); return; } - post_op.WriteFields(step, time); - Mpi::Barrier(post_op.GetComm()); + using VT = IoData::ValueType; + using fmt::format; + + auto probe_field = post_op.ProbeEField(); + MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), + format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", + v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), + probe_field.size())) + + if (!root_) + { + return; + } + + probe_E.table["idx"] << idx_value_dimensionful; + size_t i = 0; + for (const auto &idx : post_op.GetProbes()) + { + for (int i_dim = 0; i_dim < v_dim; i_dim++) + { + auto val = iodata.DimensionalizeValue(VT::FIELD_E, probe_field[i * v_dim + i_dim]); + probe_E.table[format("E{}_{}_re", idx, i_dim)] << val.real(); + if (has_imag) + { + probe_E.table[format("E{}_{}_im", idx, i_dim)] << val.imag(); + } + } + i++; + } + probe_E.WriteFullTableTrunc(); } -void BaseSolver::PostprocessErrorIndicator(const PostOperator &post_op, - const ErrorIndicator &indicator, - bool fields) const +void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful, + const PostOperator &post_op, + const IoData &iodata) { - // Write the indicator statistics. - if (post_dir.length() == 0) + if (!do_measurement_B_) + { + return; + } + using VT = IoData::ValueType; + using fmt::format; + + auto probe_field = post_op.ProbeBField(); + MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), + format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", + v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), + probe_field.size())) + + if (!root_) + { + return; + } + + probe_B.table["idx"] << idx_value_dimensionful; + size_t i = 0; + for (const auto &idx : post_op.GetProbes()) + { + for (int i_dim = 0; i_dim < v_dim; i_dim++) + { + auto val = iodata.DimensionalizeValue(VT::FIELD_B, probe_field[i * v_dim + i_dim]); + probe_B.table[format("B{}_{}_re", idx, i_dim)] << val.real(); + if (has_imag) + { + probe_B.table[format("B{}_{}_im", idx, i_dim)] << val.imag(); + } + } + i++; + } + probe_B.WriteFullTableTrunc(); +} + +void BaseSolver::ProbePostPrinter::AddMeasurement(double idx_value_dimensionful, + const PostOperator &post_op, + const IoData &iodata) +{ +#if defined(MFEM_USE_GSLIB) + AddMeasurementE(idx_value_dimensionful, post_op, iodata); + AddMeasurementB(idx_value_dimensionful, post_op, iodata); +#endif +} + +BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter( + bool do_measurement, bool root, const std::string &post_dir) + : root_{root}, do_measurement_{ + do_measurement // + && post_dir.length() > 0 // Valid output dir + } +{ + if (!do_measurement_ || !root_) { return; } + + error_indicator = TableWithCSVFile(post_dir + "error-indicators.csv"); + error_indicator.table.reserve(1, 4); + + error_indicator.table.insert_column("norm", "Norm"); + error_indicator.table.insert_column("min", "Minimum"); + error_indicator.table.insert_column("max", "Maximum"); + error_indicator.table.insert_column("mean", "Mean"); +} + +void BaseSolver::ErrorIndicatorPostPrinter::PrintIndicatorStatistics( + const PostOperator &post_op, const ErrorIndicator &indicator) +{ + if (!do_measurement_) + { + return; + } + + // MPI Gather MPI_Comm comm = post_op.GetComm(); std::array data = {indicator.Norml2(comm), indicator.Min(comm), indicator.Max(comm), indicator.Mean(comm)}; - if (root) + + if (!root_) { - std::string path = post_dir + "error-indicators.csv"; - auto output = OutputFile(path, false); - // clang-format off - output.print("{:>{}s},{:>{}s},{:>{}s},{:>{}s}\n", - "Norm", table.w, - "Minimum", table.w, - "Maximum", table.w, - "Mean", table.w); - output.print("{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e}\n", - data[0], table.w, table.p, - data[1], table.w, table.p, - data[2], table.w, table.p, - data[3], table.w, table.p); - // clang-format on - } - if (fields) - { - BlockTimer bt(Timer::IO); - post_op.WriteFieldsFinal(&indicator); - Mpi::Barrier(post_op.GetComm()); + return; } + + error_indicator.table["norm"] << data[0]; + error_indicator.table["min"] << data[1]; + error_indicator.table["max"] << data[2]; + error_indicator.table["mean"] << data[3]; + + error_indicator.WriteFullTableTrunc(); } template void BaseSolver::SaveMetadata(const KspSolver &) const; diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index 63bf1a720..7c2082bd2 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -8,14 +8,16 @@ #include #include #include +#include "utils/tablecsv.hpp" namespace palace { +class DomainPostOperator; class ErrorIndicator; class FiniteElementSpaceHierarchy; -class Mesh; class IoData; +class Mesh; class PostOperator; class Timer; @@ -29,48 +31,90 @@ class BaseSolver const IoData &iodata; // Parameters for writing postprocessing outputs. - const std::string post_dir; - const bool root; + std::string post_dir; + bool root; - // Table formatting for output files. - struct Table + // Common domain postprocessing for all simulation types. + class DomainsPostPrinter { - int w; // Total column width = precision + spaces + 7 extra (signs/exponent) - int sp; // Table column spaces - int p; // Floating point precision for data - int w1; // First column width = precision + 7 extra - int p1; // Floating point precision for first column - Table(int sp, int p, int p1) : w(sp + p + 7), sp(sp), p(p), w1(p1 + 7), p1(p1) {} + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile domain_E = {}; + + public: + DomainsPostPrinter() = default; + DomainsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const DomainPostOperator &dom_post_op, + const std::string &idx_col_name, int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + double E_elec, double E_mag, double E_cap, double E_ind, + const IoData &iodata); }; - const Table table; - - // Helper method for creating/appending to output files. - fmt::ostream OutputFile(const std::string &path, bool append) const - { - return append ? fmt::output_file(path, fmt::file::WRONLY | fmt::file::APPEND) - : fmt::output_file(path, fmt::file::WRONLY | fmt::file::CREATE | - fmt::file::TRUNC); - } - - // Common domain postprocessing for all simulation types. - void PostprocessDomains(const PostOperator &post_op, const std::string &name, int step, - double time, double E_elec, double E_mag, double E_cap, - double E_ind) const; // Common surface postprocessing for all simulation types. - void PostprocessSurfaces(const PostOperator &post_op, const std::string &name, int step, - double time, double E_elec, double E_mag) const; + class SurfacesPostPrinter + { + bool root_ = false; + bool do_measurement_flux_ = false; + bool do_measurement_eps_ = false; + TableWithCSVFile surface_F = {}; + TableWithCSVFile surface_Q = {}; + + public: + SurfacesPostPrinter() = default; + SurfacesPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const PostOperator &post_op, const std::string &idx_col_name, + int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + double E_elec, double E_mag, const IoData &iodata); + void AddMeasurementFlux(double idx_value_dimensionful, const PostOperator &post_op, + double E_elec, double E_mag, const IoData &iodata); + void AddMeasurementEps(double idx_value_dimensionful, const PostOperator &post_op, + double E_elec, double E_mag, const IoData &iodata); + }; // Common probe postprocessing for all simulation types. - void PostprocessProbes(const PostOperator &post_op, const std::string &name, int step, - double time) const; + class ProbePostPrinter + { + bool root_ = false; + bool do_measurement_E_ = false; + bool do_measurement_B_ = false; + TableWithCSVFile probe_E = {}; + TableWithCSVFile probe_B = {}; + + int v_dim = 0; + bool has_imag = false; + + public: + ProbePostPrinter() = default; + ProbePostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const PostOperator &post_op, const std::string &idx_col_name, + int n_expected_rows); + + void AddMeasurementE(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); + void AddMeasurementB(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); + }; - // Common field visualization postprocessing for all simulation types. - void PostprocessFields(const PostOperator &post_op, int step, double time) const; + // Common error indicator postprocessing for all simulation types. // + // This is trivial since data is only added at the end of the solve, rather after each + // step (time / frequency / eigenvector). + class ErrorIndicatorPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile error_indicator = {}; + + public: + ErrorIndicatorPostPrinter() = default; + ErrorIndicatorPostPrinter(bool do_measurement, bool root, const std::string &post_dir); - // Common error indicator postprocessing for all simulation types. - void PostprocessErrorIndicator(const PostOperator &post_op, - const ErrorIndicator &indicator, bool fields) const; + void PrintIndicatorStatistics(const PostOperator &post_op, + const ErrorIndicator &indicator); + }; // Performs a solve using the mesh sequence, then reports error indicators and the number // of global true dofs. diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index c447b8c24..f6f0a91ef 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -20,6 +20,7 @@ #include "utils/communication.hpp" #include "utils/iodata.hpp" #include "utils/prettyprint.hpp" +#include "utils/tablecsv.hpp" #include "utils/timer.hpp" namespace palace @@ -50,6 +51,9 @@ DrivenSolver::Solve(const std::vector> &mesh) const // Frequencies will be sampled uniformly in the frequency domain. Index sets are for // computing things like S-parameters in postprocessing. PostOperator post_op(iodata, space_op, "driven"); + PostprocessPrintResults post_results(root, post_dir, post_op, space_op, n_step, + iodata.solver.driven.delta_post); + { Mpi::Print("\nComputing {}frequency response for:\n", adaptive ? "adaptive fast " : ""); bool first = true; @@ -99,13 +103,16 @@ DrivenSolver::Solve(const std::vector> &mesh) const Mpi::Print("\n"); // Main frequency sweep loop. - return {adaptive ? SweepAdaptive(space_op, post_op, n_step, step0, omega0, delta_omega) - : SweepUniform(space_op, post_op, n_step, step0, omega0, delta_omega), + return {adaptive ? SweepAdaptive(space_op, post_op, post_results, n_step, step0, omega0, + delta_omega) + : SweepUniform(space_op, post_op, post_results, n_step, step0, omega0, + delta_omega), space_op.GlobalTrueVSize()}; } ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator &post_op, - int n_step, int step0, double omega0, + PostprocessPrintResults &post_results, int n_step, + int step0, double omega0, double delta_omega) const { // Construct the system matrices defining the linear operator. PEC boundaries are handled @@ -192,26 +199,25 @@ ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, E_mag * J, (E_elec + E_mag) * J); } - // Calculate and record the error indicators. Mpi::Print(" Updating solution error estimates\n"); estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); - // Postprocess S-parameters and optionally write solution to disk. - Postprocess(post_op, space_op.GetLumpedPortOp(), space_op.GetWavePortOp(), - space_op.GetSurfaceCurrentOp(), step, omega, E_elec, E_mag, - (step == n_step - 1) ? &indicator : nullptr); + post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); // Increment frequency. step++; omega += delta_omega; } + // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); SaveMetadata(ksp); + post_results.PostprocessFinal(post_op, indicator); return indicator; } ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator &post_op, + PostprocessPrintResults &post_results, int n_step, int step0, double omega0, double delta_omega) const { @@ -372,18 +378,16 @@ ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, E_mag * J, (E_elec + E_mag) * J); } - - // Postprocess S-parameters and optionally write solution to disk. - Postprocess(post_op, space_op.GetLumpedPortOp(), space_op.GetWavePortOp(), - space_op.GetSurfaceCurrentOp(), step, omega, E_elec, E_mag, - (step == n_step - 1) ? &indicator : nullptr); + post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); // Increment frequency. step++; omega += delta_omega; } + // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); SaveMetadata(prom_op.GetLinearSolver()); + post_results.PostprocessFinal(post_op, indicator); return indicator; } @@ -402,273 +406,158 @@ int DrivenSolver::GetNumSteps(double start, double end, double delta) const (delta > 0.0 && dfinal - end < delta_eps * end)); } -void DrivenSolver::Postprocess(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, - const SurfaceCurrentOperator &surf_j_op, int step, - double omega, double E_elec, double E_mag, - const ErrorIndicator *indicator) const +// ----------------- +// Measurements / Postprocessing + +DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( + bool do_measurement, bool root, const std::string &post_dir, + const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) + : do_measurement_{do_measurement}, root_{root} { - // The internal GridFunctions for PostOperator have already been set from the E and B - // solutions in the main frequency sweep loop. - const double freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); - const double E_cap = post_op.GetLumpedCapacitorEnergy(lumped_port_op); - const double E_ind = post_op.GetLumpedInductorEnergy(lumped_port_op); - PostprocessCurrents(post_op, surf_j_op, step, omega); - PostprocessPorts(post_op, lumped_port_op, step, omega); - if (surf_j_op.Size() == 0) - { - PostprocessSParameters(post_op, lumped_port_op, wave_port_op, step, omega); - } - PostprocessDomains(post_op, "f (GHz)", step, freq, E_elec, E_mag, E_cap, E_ind); - PostprocessSurfaces(post_op, "f (GHz)", step, freq, E_elec + E_cap, E_mag + E_ind); - PostprocessProbes(post_op, "f (GHz)", step, freq); - if (iodata.solver.driven.delta_post > 0 && step % iodata.solver.driven.delta_post == 0) + do_measurement_ = do_measurement_ // + && post_dir.length() > 0 // Valid output dir + && (surf_j_op.Size() > 0); // Needs surface currents + + if (!do_measurement_ || !root_) { - Mpi::Print("\n"); - PostprocessFields(post_op, step / iodata.solver.driven.delta_post, freq); - Mpi::Print(" Wrote fields to disk at step {:d}\n", step + 1); + return; } - if (indicator) + surface_I = TableWithCSVFile(post_dir + "surface-I.csv"); + surface_I.table.reserve(n_expected_rows, surf_j_op.Size()); + surface_I.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); + for (const auto &[idx, data] : surf_j_op) { - PostprocessErrorIndicator(post_op, *indicator, iodata.solver.driven.delta_post > 0); + surface_I.table.insert_column(fmt::format("I_{}", idx), + fmt::format("I_inc[{}] (A)", idx)); } + surface_I.AppendHeader(); } -namespace -{ - -struct CurrentData +void DrivenSolver::CurrentsPostPrinter::AddMeasurement( + double omega, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) { - const int idx; // Current source index - const double I_inc; // Excitation current -}; + if (!do_measurement_ || !root_) + { + return; + } + using VT = IoData::ValueType; + using fmt::format; -struct PortVIData -{ - const int idx; // Lumped port index - const bool excitation; // Flag for excited ports - const double V_inc, I_inc; // Incident voltage, current - const std::complex V_i, I_i; // Port voltage, current -}; + surface_I.table["idx"] << iodata.DimensionalizeValue(VT::FREQUENCY, omega); + for (const auto &[idx, data] : surf_j_op) + { + auto I_inc = data.GetExcitationCurrent(); + surface_I.table[format("I_{}", idx)] << iodata.DimensionalizeValue(VT::CURRENT, I_inc); + } + surface_I.AppendRow(); +} -struct PortSData +DrivenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, + int n_expected_rows) + : do_measurement_{do_measurement}, root_{root} { - const int idx; // Port index - const std::complex S_ij; // Scattering parameter -}; + do_measurement_ = do_measurement_ // + && post_dir.length() > 0 // Valid output dir + && (lumped_port_op.Size() > 0); // Only works for lumped ports -} // namespace - -void DrivenSolver::PostprocessCurrents(const PostOperator &post_op, - const SurfaceCurrentOperator &surf_j_op, int step, - double omega) const -{ - // Postprocess the frequency domain surface current excitations. - if (post_dir.length() == 0) + if (!do_measurement_ || !root_) { return; } - std::vector j_data; - j_data.reserve(surf_j_op.Size()); - for (const auto &[idx, data] : surf_j_op) - { - const double I_inc = data.GetExcitationCurrent(); - j_data.push_back({idx, iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_inc)}); - } - if (root && !j_data.empty()) + using fmt::format; + port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_V.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); + + port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_I.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); + + for (const auto &[idx, data] : lumped_port_op) { - std::string path = post_dir + "surface-I.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", "f (GHz)", table.w1); - for (const auto &data : j_data) - { - // clang-format off - output.print("{:>{}s}{}", - "I_inc[" + std::to_string(data.idx) + "] (A)", table.w, - (data.idx == j_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega), - table.w1, table.p1); - // clang-format on - for (const auto &data : j_data) + if (data.excitation) { - // clang-format off - output.print("{:+{}.{}e}{}", - data.I_inc, table.w, table.p, - (data.idx == j_data.back().idx) ? "" : ","); - // clang-format on + port_V.table.insert_column(format("inc{}", idx), format("V_inc[{}] (V)", idx)); + port_I.table.insert_column(format("inc{}", idx), format("I_inc[{}] (A)", idx)); } - output.print("\n"); + + port_V.table.insert_column(format("re{}", idx), format("Re{{V[{}]}} (V)", idx)); + port_V.table.insert_column(format("im{}", idx), format("Im{{V[{}]}} (V)", idx)); + + port_I.table.insert_column(format("re{}", idx), format("Re{{I[{}]}} (A)", idx)); + port_I.table.insert_column(format("im{}", idx), format("Im{{I[{}]}} (A)", idx)); } + port_V.AppendHeader(); + port_I.AppendHeader(); } -void DrivenSolver::PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int step, - double omega) const +void DrivenSolver::PortsPostPrinter::AddMeasurement( + double omega, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, + const IoData &iodata) { - // Postprocess the frequency domain lumped port voltages and currents (complex magnitude - // = sqrt(2) * RMS). - if (post_dir.length() == 0) + if (!do_measurement_ || !root_) { return; } - std::vector port_data; - port_data.reserve(lumped_port_op.Size()); + using VT = IoData::ValueType; + + // Postprocess the frequency domain lumped port voltages and currents (complex magnitude + // = sqrt(2) * RMS). + auto freq = iodata.DimensionalizeValue(VT::FREQUENCY, omega); + port_V.table["idx"] << freq; + port_I.table["idx"] << freq; + + auto unit_V = iodata.DimensionalizeValue(VT::VOLTAGE, 1.0); + auto unit_A = iodata.DimensionalizeValue(VT::CURRENT, 1.0); + for (const auto &[idx, data] : lumped_port_op) { - const double V_inc = data.GetExcitationVoltage(); - const double I_inc = (std::abs(V_inc) > 0.0) ? data.GetExcitationPower() / V_inc : 0.0; - const std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); - const std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); - port_data.push_back({idx, data.excitation, - iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, V_inc), - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_inc), - iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, V_i), - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_i)}); - } - if (root && !port_data.empty()) - { - // Write the port voltages. + if (data.excitation) { - std::string path = post_dir + "port-V.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", "f (GHz)", table.w1); - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:>{}s},", - "V_inc[" + std::to_string(data.idx) + "] (V)", table.w); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Re{V[" + std::to_string(data.idx) + "]} (V)", table.w, - "Im{V[" + std::to_string(data.idx) + "]} (V)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega), - table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:+{}.{}e},", - data.V_inc, table.w, table.p); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.V_i.real(), table.w, table.p, - data.V_i.imag(), table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } + double V_inc = data.GetExcitationVoltage(); + double I_inc = (std::abs(V_inc) > 0.0) ? data.GetExcitationPower() / V_inc : 0.0; - // Write the port currents. - { - std::string path = post_dir + "port-I.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", "f (GHz)", table.w1); - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:>{}s},", - "I_inc[" + std::to_string(data.idx) + "] (A)", table.w); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Re{I[" + std::to_string(data.idx) + "]} (A)", table.w, - "Im{I[" + std::to_string(data.idx) + "]} (A)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega), - table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:+{}.{}e},", - data.I_inc, table.w, table.p); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.I_i.real(), table.w, table.p, - data.I_i.imag(), table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + port_V.table[fmt::format("inc{}", idx)] << V_inc * unit_V; + port_I.table[fmt::format("inc{}", idx)] << I_inc * unit_A; } + + std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); + std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + + port_V.table[fmt::format("re{}", idx)] << V_i.real() * unit_V; + port_V.table[fmt::format("im{}", idx)] << V_i.imag() * unit_V; + + port_I.table[fmt::format("re{}", idx)] << I_i.real() * unit_A; + port_I.table[fmt::format("im{}", idx)] << I_i.imag() * unit_A; } + port_V.AppendRow(); + port_I.AppendRow(); } -void DrivenSolver::PostprocessSParameters(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, int step, - double omega) const +DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( + bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, + int n_expected_rows) + : do_measurement_{do_measurement}, root_{root}, src_lumped_port{lumped_port_op.Size() > 0} { - // Postprocess S-parameters. This computes a column of the S matrix corresponding to the - // excited port index specified in the configuration file, storing |S_ij| and arg - // (S_ij) in dB and degrees, respectively. S-parameter output is only available for a - // single lumped or wave port excitation. - bool src_lumped_port = false; - bool src_wave_port = false; - int source_idx = -1; + do_measurement_ = do_measurement_ // + && (post_dir.length() > 0) // valid output dir + && (src_lumped_port xor + (wave_port_op.Size() > 0)); // either lumped or wave but not both + + if (!do_measurement_ || !root_) + { + return; + } + + // Get excitation index as is currently done: if -1 then no excitation + // Already ensured that one of lumped or wave ports are empty for (const auto &[idx, data] : lumped_port_op) { if (data.excitation) { - if (src_lumped_port || src_wave_port) - { - return; - } - src_lumped_port = true; source_idx = idx; } } @@ -676,89 +565,156 @@ void DrivenSolver::PostprocessSParameters(const PostOperator &post_op, { if (data.excitation) { - if (src_lumped_port || src_wave_port) - { - return; - } - src_wave_port = true; source_idx = idx; } } - if (!src_lumped_port && !src_wave_port) + + do_measurement_ = do_measurement_ && (source_idx > 0); + + if (!do_measurement_ || !root_) { return; } - std::vector port_data; - port_data.reserve(src_lumped_port ? lumped_port_op.Size() : wave_port_op.Size()); - if (src_lumped_port) + + using fmt::format; + port_S = TableWithCSVFile(post_dir + "port-S.csv"); + port_S.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_S.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); + + // Already ensured that one of lumped or wave ports are empty + for (const auto &[o_idx, data] : lumped_port_op) { - // Compute lumped port S-parameters. - for (const auto &[idx, data] : lumped_port_op) - { - const std::complex S_ij = - post_op.GetSParameter(lumped_port_op, idx, source_idx); - port_data.push_back({idx, S_ij}); - } + port_S.table.insert_column(format("abs_{}_{}", o_idx, source_idx), + format("|S[{}][{}]| (dB)", o_idx, source_idx)); + port_S.table.insert_column(format("arg_{}_{}", o_idx, source_idx), + format("arg(S[{}][{}]) (deg.)", o_idx, source_idx)); } - else // src_wave_port + for (const auto &[o_idx, data] : wave_port_op) { - // Compute wave port S-parameters. - for (const auto &[idx, data] : wave_port_op) - { - const std::complex S_ij = - post_op.GetSParameter(wave_port_op, idx, source_idx); - port_data.push_back({idx, S_ij}); - } + port_S.table.insert_column(format("abs_{}_{}", o_idx, source_idx), + format("|S[{}][{}]| (dB)", o_idx, source_idx)); + port_S.table.insert_column(format("arg_{}_{}", o_idx, source_idx), + format("arg(S[{}][{}]) (deg.)", o_idx, source_idx)); } + port_S.AppendHeader(); +} - // Print table to stdout. - for (const auto &data : port_data) +void DrivenSolver::SParametersPostPrinter::AddMeasurement( + double omega, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, + const WavePortOperator &wave_port_op, const IoData &iodata) +{ + if (!do_measurement_ || !root_) { - std::string str = - "S[" + std::to_string(data.idx) + "][" + std::to_string(source_idx) + "]"; - // clang-format off - Mpi::Print(" {} = {:+.3e}{:+.3e}i, |{}| = {:+.3e}, arg({}) = {:+.3e}\n", - str, data.S_ij.real(), data.S_ij.imag(), - str, 20.0 * std::log10(std::abs(data.S_ij)), - str, std::arg(data.S_ij) * 180.0 / M_PI); - // clang-format on + return; } + using VT = IoData::ValueType; + using fmt::format; - // Print table to file. - if (root && post_dir.length() > 0) + // Add frequencies + port_S.table["idx"] << iodata.DimensionalizeValue(VT::FREQUENCY, omega); + + std::vector all_port_indices; + for (const auto &[idx, data] : lumped_port_op) { - std::string path = post_dir + "port-S.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) + all_port_indices.emplace_back(idx); + } + for (const auto &[idx, data] : wave_port_op) + { + all_port_indices.emplace_back(idx); + } + + for (const auto o_idx : all_port_indices) + { + std::complex S_ij; + if (src_lumped_port) { - output.print("{:>{}s},", "f (GHz)", table.w1); - for (const auto &data : port_data) - { - std::string str = - "S[" + std::to_string(data.idx) + "][" + std::to_string(source_idx) + "]"; - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "|" + str + "| (dB)", table.w, - "arg(" + str + ") (deg.)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + S_ij = post_op.GetSParameter(lumped_port_op, o_idx, source_idx); + } + else + { + S_ij = post_op.GetSParameter(wave_port_op, o_idx, source_idx); + } + auto abs_S_ij = 20.0 * std::log10(std::abs(S_ij)); + auto arg_S_ij = std::arg(S_ij) * 180.8 / M_PI; + + port_S.table[format("abs_{}_{}", o_idx, source_idx)] << abs_S_ij; + port_S.table[format("arg_{}_{}", o_idx, source_idx)] << arg_S_ij; + + Mpi::Print(" {sij} = {:+.3e}{:+.3e}i, |{sij}| = {:+.3e}, arg({sij}) = {:+.3e}\n", + S_ij.real(), S_ij.imag(), abs_S_ij, arg_S_ij, + fmt::arg("sij", format("S[{}][{}]", o_idx, source_idx))); + } + // Regenerate from scratch each time since not row-wise (TODO: improve) + port_S.WriteFullTableTrunc(); +} + +DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( + bool root, const std::string &post_dir, const PostOperator &post_op, + const SpaceOperator &space_op, int n_expected_rows, int delta_post_) + : delta_post{delta_post_}, + domains{true, root, post_dir, post_op.GetDomainPostOp(), "f (GHz)", n_expected_rows}, + surfaces{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, + currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, + probes{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, + ports{true, root, post_dir, space_op.GetLumpedPortOp(), n_expected_rows}, + s_parameters{true, + root, + post_dir, + space_op.GetLumpedPortOp(), + space_op.GetWavePortOp(), + n_expected_rows}, + error_indicator{true, root, post_dir} +{ + // If to print paraview fields + if (delta_post > 0) + { + if (post_dir.length() == 0) + { + Mpi::Warning(post_op.GetComm(), + "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " + "fields to disk in solve!\n"); } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega), - table.w1, table.p1); - for (const auto &data : port_data) + else { - // clang-format off - output.print("{:>+{}.{}e},{:>+{}.{}e}{}", - 20.0 * std::log10(std::abs(data.S_ij)), table.w, table.p, - std::arg(data.S_ij) * 180.0 / M_PI, table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on + write_paraview_fields = true; } - output.print("\n"); + } +} + +void DrivenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata, + const PostOperator &post_op, + const SpaceOperator &space_op, + int step, double omega, + double E_elec, double E_mag) +{ + auto freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); + auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); + auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); + + domains.AddMeasurement(freq, post_op, E_elec, E_mag, E_cap, E_ind, iodata); + surfaces.AddMeasurement(freq, post_op, E_elec + E_cap, E_mag + E_ind, iodata); + currents.AddMeasurement(omega, space_op.GetSurfaceCurrentOp(), iodata); + probes.AddMeasurement(freq, post_op, iodata); + ports.AddMeasurement(omega, post_op, space_op.GetLumpedPortOp(), iodata); + s_parameters.AddMeasurement(omega, post_op, space_op.GetLumpedPortOp(), + space_op.GetWavePortOp(), iodata); + // The internal GridFunctions in PostOperator have already been set: + if (write_paraview_fields && (step % delta_post == 0)) + { + Mpi::Print("\n"); + post_op.WriteFields(step / delta_post, freq); + Mpi::Print(" Wrote fields to disk at step {:d}\n", step + 1); + } +} + +void DrivenSolver::PostprocessPrintResults::PostprocessFinal( + const PostOperator &post_op, const ErrorIndicator &indicator) +{ + BlockTimer bt0(Timer::POSTPRO); + error_indicator.PrintIndicatorStatistics(post_op, indicator); + if (write_paraview_fields) + { + post_op.WriteFieldsFinal(&indicator); } } diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index 67a6413e6..4f815a1d6 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -7,6 +7,7 @@ #include #include #include "drivers/basesolver.hpp" +#include "utils/tablecsv.hpp" namespace palace { @@ -27,29 +28,92 @@ class DrivenSolver : public BaseSolver private: int GetNumSteps(double start, double end, double delta) const; - ErrorIndicator SweepUniform(SpaceOperator &space_op, PostOperator &post_op, int n_step, - int step0, double omega0, double delta_omega) const; + // Printers for storing and printing postprocessing mesurements - ErrorIndicator SweepAdaptive(SpaceOperator &space_op, PostOperator &post_op, int n_step, - int step0, double omega0, double delta_omega) const; + class CurrentsPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile surface_I = {}; - void Postprocess(const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, - const SurfaceCurrentOperator &surf_j_op, int step, double omega, - double E_elec, double E_mag, const ErrorIndicator *indicator) const; + public: + CurrentsPostPrinter() = default; + CurrentsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); + void AddMeasurement(double omega, const SurfaceCurrentOperator &surf_j_op, + const IoData &iodata); + }; - void PostprocessCurrents(const PostOperator &post_op, - const SurfaceCurrentOperator &surf_j_op, int step, - double omega) const; + class PortsPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile port_V = {}; + TableWithCSVFile port_I = {}; - void PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int step, - double omega) const; + public: + PortsPostPrinter() = default; + PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, int n_expected_rows); + void AddMeasurement(double omega, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, const IoData &iodata); + }; - void PostprocessSParameters(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, int step, - double omega) const; + class SParametersPostPrinter + { + // Postprocess S-parameters. This computes a column of the S matrix corresponding to the + // excited port index specified in the configuration file, storing |S_ij| and arg + // (S_ij) in dB and degrees, respectively. S-parameter output is only available for a + // single lumped or wave port excitation. + + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile port_S = {}; + + // Currently can't mix lumped and sufrace ports for s-matrix + bool src_lumped_port = true; + int source_idx = -1; + + public: + SParametersPostPrinter() = default; + SParametersPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, + const WavePortOperator &wave_port_op, int n_expected_rows); + void AddMeasurement(double omega, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + const WavePortOperator &wave_port_op, const IoData &iodata); + }; + + struct PostprocessPrintResults + { + bool write_paraview_fields = false; + int delta_post = 0; + + DomainsPostPrinter domains; + SurfacesPostPrinter surfaces; + CurrentsPostPrinter currents; + ProbePostPrinter probes; + PortsPostPrinter ports; + SParametersPostPrinter s_parameters; + + ErrorIndicatorPostPrinter error_indicator; + + PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + const PostOperator &post_op, const SpaceOperator &space_op, + int n_expected_rows, int delta_post); + void PostprocessStep(const IoData &iodata, const PostOperator &post_op, + const SpaceOperator &space_op, int step, double omega, + double E_elec, double E_mag); + void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); + }; + + ErrorIndicator SweepUniform(SpaceOperator &space_op, PostOperator &post_op, + PostprocessPrintResults &post_results, int n_step, int step0, + double omega0, double delta_omega) const; + + ErrorIndicator SweepAdaptive(SpaceOperator &space_op, PostOperator &post_op, + PostprocessPrintResults &post_results, int n_step, int step0, + double omega0, double delta_omega) const; std::pair Solve(const std::vector> &mesh) const override; diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index cda6efabd..a56faddfa 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -41,6 +41,8 @@ EigenSolver::Solve(const std::vector> &mesh) const // Configure objects for postprocessing. PostOperator post_op(iodata, space_op, "eigenmode"); + PostprocessPrintResults post_results(root, post_dir, post_op, space_op, + iodata.solver.eigenmode.n_post); ComplexVector E(Curl.Width()), B(Curl.Height()); E.UseDevice(true); B.UseDevice(true); @@ -272,6 +274,9 @@ EigenSolver::Solve(const std::vector> &mesh) const BlockTimer bt2(Timer::POSTPRO); SaveMetadata(*ksp); + // Update printer now we know num_conv + post_results.eigen.stdout_int_print_width = 1 + static_cast(std::log10(num_conv)); + // Calculate and record the error indicators, and postprocess the results. Mpi::Print("\nComputing solution error estimates and performing postprocessing\n"); if (!KM) @@ -283,6 +288,8 @@ EigenSolver::Solve(const std::vector> &mesh) const eigen->RescaleEigenvectors(num_conv); } Mpi::Print("\n"); + + post_results.eigen.PrintStdoutHeader(); // Print headerline for logfile mode for (int i = 0; i < num_conv; i++) { // Get the eigenvalue and relative error. @@ -318,315 +325,417 @@ EigenSolver::Solve(const std::vector> &mesh) const estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); } - // Postprocess the mode. - Postprocess(post_op, space_op.GetLumpedPortOp(), i, omega, error_bkwd, error_abs, - num_conv, E_elec, E_mag, - (i == iodata.solver.eigenmode.n - 1) ? &indicator : nullptr); - } - return {indicator, space_op.GlobalTrueVSize()}; -} + // Postprocess state and write fields to file + post_results.PostprocessStep(iodata, post_op, space_op, i, omega, E_elec, E_mag, + error_abs, error_bkwd); -void EigenSolver::Postprocess(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int i, - std::complex omega, double error_bkwd, - double error_abs, int num_conv, double E_elec, double E_mag, - const ErrorIndicator *indicator) const -{ - // The internal GridFunctions for PostOperator have already been set from the E and B - // solutions in the main loop over converged eigenvalues. - const double E_cap = post_op.GetLumpedCapacitorEnergy(lumped_port_op); - const double E_ind = post_op.GetLumpedInductorEnergy(lumped_port_op); - PostprocessEigen(i, omega, error_bkwd, error_abs, num_conv); - PostprocessPorts(post_op, lumped_port_op, i); - PostprocessEPR(post_op, lumped_port_op, i, omega, E_elec + E_cap); - PostprocessDomains(post_op, "m", i, i + 1, E_elec, E_mag, E_cap, E_ind); - PostprocessSurfaces(post_op, "m", i, i + 1, E_elec + E_cap, E_mag + E_ind); - PostprocessProbes(post_op, "m", i, i + 1); - if (i < iodata.solver.eigenmode.n_post) - { - PostprocessFields(post_op, i, i + 1); - Mpi::Print(" Wrote mode {:d} to disk\n", i + 1); - } - if (indicator) - { - PostprocessErrorIndicator(post_op, *indicator, iodata.solver.eigenmode.n_post > 0); + // Final write: Different condition than end of loop (i = num_conv - 1) + if (i == iodata.solver.eigenmode.n - 1) + { + post_results.PostprocessFinal(post_op, indicator); + } } + return {indicator, space_op.GlobalTrueVSize()}; } namespace { -struct PortVIData -{ - const int idx; // Lumped port index - const std::complex V_i, I_i; // Port voltage, current -}; - struct EprLData { - const int idx; // Lumped port index - const double pj; // Inductor energy-participation ratio + int idx; // Lumped port index + double pj; // Inductor energy-participation ratio }; struct EprIOData { - const int idx; // Lumped port index - const double Ql; // Quality factor - const double Kl; // κ for loss rate + int idx; // Lumped port index + double Ql; // Quality factor + double Kl; // κ for loss rate }; } // namespace -void EigenSolver::PostprocessEigen(int i, std::complex omega, double error_bkwd, - double error_abs, int num_conv) const +void EigenSolver::EigenPostPrinter::PrintStdoutHeader() { - // Dimensionalize the result and print in a nice table of frequencies and Q-factors. Save - // to file if user has specified. - const std::complex f = { - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega.real()), - iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega.imag())}; - const double Q = - (f.imag() == 0.0) ? mfem::infinity() : 0.5 * std::abs(f) / std::abs(f.imag()); - - // Print table to stdout. - { - const int int_width = 1 + static_cast(std::log10(num_conv)); - constexpr int p = 6; - constexpr int w = 6 + p + 7; // Column spaces + precision + extra for table - if (i == 0) + if (!root_) + { + return; + } + auto save_defaults(eig.table.col_options); + + eig.table.col_options.float_precision = 6; + eig.table.col_options.min_left_padding = 6; + + // Separate printing due to printing lead as integer not float + + fmt::memory_buffer buf; + auto to = [&buf](auto f, auto &&...a) + { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; + + to("{}", eig.table[0].format_header(stdout_int_print_width)); + for (int i = 1; i < eig.table.n_cols(); i++) + { + if (i > 0) { - // clang-format off - Mpi::Print("{:>{}s}{:>{}s}{:>{}s}{:>{}s}{:>{}s}\n{}\n", - "m", int_width, - "Re{ω}/2π (GHz)", w, - "Im{ω}/2π (GHz)", w, - "Bkwd. Error", w, - "Abs. Error", w, - std::string(int_width + 4 * w, '=')); - // clang-format on + to("{:s}", eig.table.print_col_separator); } - // clang-format off - Mpi::Print("{:{}d}{:+{}.{}e}{:+{}.{}e}{:+{}.{}e}{:+{}.{}e}\n", - i + 1, int_width, - f.real(), w, p, - f.imag(), w, p, - error_bkwd, w, p, - error_abs, w, p); - // clang-format on - } - - // Print table to file. - if (root && post_dir.length() > 0) - { - std::string path = post_dir + "eig.csv"; - auto output = OutputFile(path, (i > 0)); - if (i == 0) + to("{:s}", eig.table[i].format_header()); + } + to("{:s}", eig.table.print_row_separator); + + Mpi::Print("{}{}", std::string{buf.data(), buf.size()}, + std::string(stdout_int_print_width + 4 * eig.table[1].col_width(), '=')); + eig.table.col_options = save_defaults; +} + +void EigenSolver::EigenPostPrinter::PrintStdoutRow(size_t j) +{ + if (!root_) + { + return; + } + auto save_defaults(eig.table.col_options); + eig.table.col_options.float_precision = 6; + eig.table.col_options.min_left_padding = 6; + + // Separate printing due to integer lead + fmt::memory_buffer buf; + auto to = [&buf](auto f, auto &&...a) + { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; + + to("{:{}d}", int(eig.table[0].data[j]), stdout_int_print_width); + for (int i = 1; i < eig.table.n_cols(); i++) + { + if (i > 0) { - // clang-format off - output.print("{:>{}s},{:>{}s},{:>{}s},{:>{}s},{:>{}s},{:>{}s}\n", - "m", table.w1, - "Re{f} (GHz)", table.w, - "Im{f} (GHz)", table.w, - "Q", table.w, - "Error (Bkwd.)", table.w, - "Error (Abs.)", table.w); - // clang-format on + to("{:s}", eig.table.print_col_separator); } - // clang-format off - output.print("{:{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e},{:+{}.{}e}\n", - static_cast(i + 1), table.w1, table.p1, - f.real(), table.w, table.p, - f.imag(), table.w, table.p, - Q, table.w, table.p, - error_bkwd, table.w, table.p, - error_abs, table.w, table.p); - // clang-format on + to("{:s}", eig.table[i].format_row(j)); } + to("{:s}", eig.table.print_row_separator); + Mpi::Print("{}", buf); + eig.table.col_options = save_defaults; } -void EigenSolver::PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int i) const +EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, int n_eig) + : root_{root}, do_measurement_(do_measurement // + && post_dir.length() > 0 // Valid output dir + ), + stdout_int_print_width(1 + static_cast(std::log10(n_eig))) { - // Postprocess the frequency domain lumped port voltages and currents (complex magnitude - // = sqrt(2) * RMS). - if (post_dir.length() == 0) + // Note: we switch to n_eig rather than n_conv for padding since we don't know n_conv + // until solve + if (!do_measurement_ || !root_) { return; } - std::vector port_data; - port_data.reserve(lumped_port_op.Size()); - for (const auto &[idx, data] : lumped_port_op) + eig = TableWithCSVFile(post_dir + "eig.csv"); + eig.table.reserve(n_eig, 6); + eig.table.insert_column(Column("idx", "m", 0, {}, {}, "")); + eig.table.insert_column("f_re", "Re{f} (GHz)"); + eig.table.insert_column("f_im", "Im{f} (GHz)"); + eig.table.insert_column("q", "Q"); + eig.table.insert_column("err_back", "Error (Bkwd.)"); + eig.table.insert_column("err_abs", "Error (Abs.)"); + eig.AppendHeader(); +} + +void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, + std::complex omega, + double error_bkwd, double error_abs, + const IoData &iodata) +{ + if (!do_measurement_ || !root_) { - const std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); - const std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); - port_data.push_back({idx, iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, V_i), - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_i)}); + return; } - if (root && !port_data.empty()) + using VT = IoData::ValueType; + using fmt::format; + + std::complex f = iodata.DimensionalizeValue(VT::FREQUENCY, omega); + double Q = (f.imag() == 0.0) ? mfem::infinity() : 0.5 * std::abs(f) / std::abs(f.imag()); + + eig.table["idx"] << eigen_print_idx; + eig.table["f_re"] << f.real(); + eig.table["f_im"] << f.imag(); + eig.table["q"] << Q; + eig.table["err_back"] << error_bkwd; + eig.table["err_abs"] << error_abs; + + eig.AppendRow(); + PrintStdoutRow(eig.table.n_rows() - 1); +} + +EigenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, + int n_expected_rows) + : do_measurement_{do_measurement}, root_{root} +{ + do_measurement_ = do_measurement_ // + && post_dir.length() > 0 // Valid output dir + && (lumped_port_op.Size() > 0); // Only works for lumped ports + + if (!do_measurement_ || !root_) { - // Write the port voltages. - { - std::string path = post_dir + "port-V.csv"; - auto output = OutputFile(path, (i > 0)); - if (i == 0) - { - output.print("{:>{}s},", "m", table.w1); - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Re{V[" + std::to_string(data.idx) + "]} (V)", table.w, - "Im{V[" + std::to_string(data.idx) + "]} (V)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - static_cast(i + 1), table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.V_i.real(), table.w, table.p, - data.V_i.imag(), table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } + return; + } + using fmt::format; + port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_V.table.insert_column(Column("idx", "m", 0, {}, {}, "")); - // Write the port currents. - { - std::string path = post_dir + "port-I.csv"; - auto output = OutputFile(path, (i > 0)); - if (i == 0) - { - output.print("{:>{}s},", "m", table.w1); - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Re{I[" + std::to_string(data.idx) + "]} (A)", table.w, - "Im{I[" + std::to_string(data.idx) + "]} (A)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - static_cast(i + 1), table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.I_i.real(), table.w, table.p, - data.I_i.imag(), table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } + port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_I.table.insert_column(Column("idx", "m", 0, {}, {}, "")); + + for (const auto &[idx, data] : lumped_port_op) + { + port_V.table.insert_column(format("re{}", idx), format("Re{{V[{}]}} (V)", idx)); + port_V.table.insert_column(format("im{}", idx), format("Im{{V[{}]}} (V)", idx)); + + port_I.table.insert_column(format("re{}", idx), format("Re{{I[{}]}} (A)", idx)); + port_I.table.insert_column(format("im{}", idx), format("Im{{I[{}]}} (A)", idx)); } + port_V.AppendHeader(); + port_I.AppendHeader(); } -void EigenSolver::PostprocessEPR(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int i, - std::complex omega, double E_m) const +void EigenSolver::PortsPostPrinter::AddMeasurement(int eigen_print_idx, + const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + const IoData &iodata) { - // If ports have been specified in the model, compute the corresponding energy- - // participation ratios (EPR) and write out to disk. - if (post_dir.length() == 0) + if (!do_measurement_ || !root_) { return; } + using VT = IoData::ValueType; + + // Postprocess the frequency domain lumped port voltages and currents (complex magnitude + // = sqrt(2) * RMS). + port_V.table["idx"] << eigen_print_idx; + port_I.table["idx"] << eigen_print_idx; + + auto unit_V = iodata.DimensionalizeValue(VT::VOLTAGE, 1.0); + auto unit_A = iodata.DimensionalizeValue(VT::CURRENT, 1.0); + + for (const auto &[idx, data] : lumped_port_op) + { + std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); + std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + + port_V.table[fmt::format("re{}", idx)] << V_i.real() * unit_V; + port_V.table[fmt::format("im{}", idx)] << V_i.imag() * unit_V; + + port_I.table[fmt::format("re{}", idx)] << I_i.real() * unit_A; + port_I.table[fmt::format("im{}", idx)] << I_i.imag() * unit_A; + } + port_V.AppendRow(); + port_I.AppendRow(); +} + +EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, + const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, + int n_expected_rows) + : root_{root}, // + do_measurement_EPR_(do_measurement // + && post_dir.length() > 0 // Valid output dir + && lumped_port_op.Size() > 0), + do_measurement_Q_(do_measurement_EPR_) +{ + // Mode EPR for lumped inductor elements: - // Write the mode EPR for lumped inductor elements. - std::vector epr_L_data; - epr_L_data.reserve(lumped_port_op.Size()); for (const auto &[idx, data] : lumped_port_op) { - if (std::abs(data.L) > 0.0) + if (std::abs(data.L) > 0.) + { + ports_with_L.push_back(idx); + } + if (std::abs(data.R) > 0.) { - const double pj = post_op.GetInductorParticipation(lumped_port_op, idx, E_m); - epr_L_data.push_back({idx, pj}); + ports_with_R.push_back(idx); } } - if (root && !epr_L_data.empty()) + + do_measurement_EPR_ = do_measurement_EPR_ && !ports_with_L.empty(); + do_measurement_Q_ = do_measurement_Q_ && !ports_with_R.empty(); + + if (!root_ || (!do_measurement_EPR_ && !do_measurement_Q_)) { - std::string path = post_dir + "port-EPR.csv"; - auto output = OutputFile(path, (i > 0)); - if (i == 0) + return; + } + using fmt::format; + + if (do_measurement_EPR_) + { + port_EPR = TableWithCSVFile(post_dir + "port-EPR.csv"); + port_EPR.table.reserve(n_expected_rows, 1 + ports_with_L.size()); + port_EPR.table.insert_column(Column("idx", "m", 0, {}, {}, "")); + for (const auto idx : ports_with_L) { - output.print("{:>{}s},", "m", table.w1); - for (const auto &data : epr_L_data) - { - // clang-format off - output.print("{:>{}s}{}", - "p[" + std::to_string(data.idx) + "]", table.w, - (data.idx == epr_L_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + port_EPR.table.insert_column(format("p_{}", idx), format("p[{}]", idx)); } - output.print("{:{}.{}e},", static_cast(i + 1), table.w1, table.p1); - for (const auto &data : epr_L_data) + port_EPR.AppendHeader(); + } + if (do_measurement_Q_) + { + port_Q = TableWithCSVFile(post_dir + "port-Q.csv"); + port_Q.table.reserve(n_expected_rows, 1 + ports_with_R.size()); + port_Q.table.insert_column(Column("idx", "m", 0, {}, {}, "")); + for (const auto idx : ports_with_L) { - // clang-format off - output.print("{:+{}.{}e}{}", - data.pj, table.w, table.p, - (data.idx == epr_L_data.back().idx) ? "" : ","); - // clang-format on + port_Q.table.insert_column(format("Ql_{}", idx), format("Q_ext[{}]", idx)); + port_Q.table.insert_column(format("Kl_{}", idx), format("κ_ext[{}] (GHz)", idx)); } - output.print("\n"); + port_Q.AppendHeader(); + } +} + +void EigenSolver::EPRPostPrinter::AddMeasurementEPR( + double eigen_print_idx, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, double E_m, const IoData &iodata) +{ + if (!do_measurement_EPR_) + { + return; + } + + // TODO: Does this include a reduce? + // Write the mode EPR for lumped inductor elements. + std::vector epr_L_data; + epr_L_data.reserve(ports_with_L.size()); + for (const auto idx : ports_with_L) + { + const double pj = post_op.GetInductorParticipation(lumped_port_op, idx, E_m); + epr_L_data.push_back({idx, pj}); } + if (!root_) + { + return; + } + using fmt::format; + + port_EPR.table["idx"] << eigen_print_idx; + for (const auto &[idx, pj] : epr_L_data) + { + port_EPR.table[format("p_{}", idx)] << pj; + } + port_EPR.AppendRow(); +} + +void EigenSolver::EPRPostPrinter::AddMeasurementQ(double eigen_print_idx, + const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + std::complex omega, double E_m, + const IoData &iodata) +{ + if (!do_measurement_Q_) + { + return; + } + + // TODO: Does this include a reduce? // Write the mode EPR for lumped resistor elements. std::vector epr_IO_data; - epr_IO_data.reserve(lumped_port_op.Size()); - for (const auto &[idx, data] : lumped_port_op) + epr_IO_data.reserve(ports_with_R.size()); + for (const auto idx : ports_with_R) { - if (std::abs(data.R) > 0.0) - { - const double Kl = post_op.GetExternalKappa(lumped_port_op, idx, E_m); - const double Ql = (Kl == 0.0) ? mfem::infinity() : omega.real() / std::abs(Kl); - epr_IO_data.push_back( - {idx, Ql, iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, Kl)}); - } + const double Kl = post_op.GetExternalKappa(lumped_port_op, idx, E_m); + const double Ql = (Kl == 0.0) ? mfem::infinity() : omega.real() / std::abs(Kl); + epr_IO_data.push_back( + {idx, Ql, iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, Kl)}); + } + + if (!root_) + { + return; } - if (root && !epr_IO_data.empty()) + using fmt::format; + + port_EPR.table["idx"] << eigen_print_idx; + for (const auto &[idx, Ql, Kl] : epr_IO_data) + { + port_Q.table[format("Ql_{}", idx)] << Ql; + port_Q.table[format("Kl_{}", idx)] << Kl; + } + port_Q.AppendRow(); +} + +void EigenSolver::EPRPostPrinter::AddMeasurement(double eigen_print_idx, + const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + std::complex omega, double E_m, + const IoData &iodata) +{ + AddMeasurementEPR(eigen_print_idx, post_op, lumped_port_op, E_m, iodata); + AddMeasurementQ(eigen_print_idx, post_op, lumped_port_op, omega, E_m, iodata); +} + +EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, + const std::string &post_dir, + const PostOperator &post_op, + const SpaceOperator &space_op, + int n_post_) + : n_post(n_post_), // + domains{true, root, post_dir, post_op.GetDomainPostOp(), "m", n_post}, + surfaces{true, root, post_dir, post_op, "m", n_post}, + probes{true, root, post_dir, post_op, "m", n_post}, eigen{true, root, post_dir, n_post}, + epr{true, root, post_dir, space_op.GetLumpedPortOp(), n_post}, + error_indicator{true, root, post_dir} +{ + if (n_post > 0) { - std::string path = post_dir + "port-Q.csv"; - auto output = OutputFile(path, (i > 0)); - if (i == 0) + if (post_dir.length() == 0) { - output.print("{:>{}s},", "m", table.w1); - for (const auto &data : epr_IO_data) - { - // clang-format off - output.print("{:>{}s},{:>{}s}{}", - "Q_ext[" + std::to_string(data.idx) + "]", table.w, - "κ_ext[" + std::to_string(data.idx) + "] (GHz)", table.w, - (data.idx == epr_IO_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + Mpi::Warning(post_op.GetComm(), + "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " + "fields to disk in solve!\n"); } - output.print("{:{}.{}e},", static_cast(i + 1), table.w1, table.p1); - for (const auto &data : epr_IO_data) + else { - // clang-format off - output.print("{:+{}.{}e},{:+{}.{}e}{}", - data.Ql, table.w, table.p, - data.Kl, table.w, table.p, - (data.idx == epr_IO_data.back().idx) ? "" : ","); - // clang-format on + write_paraview_fields = true; } - output.print("\n"); + } +} + +void EigenSolver::PostprocessPrintResults::PostprocessStep( + const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, + int step, std::complex omega, double E_elec, double E_mag, double error_abs, + double error_bkward) +{ + int eigen_print_idx = step + 1; + + auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); + auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); + + domains.AddMeasurement(eigen_print_idx, post_op, E_elec, E_mag, E_cap, E_ind, iodata); + surfaces.AddMeasurement(eigen_print_idx, post_op, E_elec + E_cap, E_mag + E_ind, iodata); + probes.AddMeasurement(eigen_print_idx, post_op, iodata); + eigen.AddMeasurement(eigen_print_idx, omega, error_bkward, error_abs, iodata); + epr.AddMeasurement(eigen_print_idx, post_op, space_op.GetLumpedPortOp(), omega, + E_elec + E_cap, iodata); + // The internal GridFunctions in PostOperator have already been set: + if (write_paraview_fields && step < n_post) + { + Mpi::Print("\n"); + post_op.WriteFields(step, eigen_print_idx); + Mpi::Print(" Wrote mode {:d} to disk\n", eigen_print_idx); + } +} + +void EigenSolver::PostprocessPrintResults::PostprocessFinal(const PostOperator &post_op, + const ErrorIndicator &indicator) +{ + BlockTimer bt0(Timer::POSTPRO); + error_indicator.PrintIndicatorStatistics(post_op, indicator); + if (write_paraview_fields) + { + post_op.WriteFieldsFinal(&indicator); } } diff --git a/palace/drivers/eigensolver.hpp b/palace/drivers/eigensolver.hpp index 355222697..0fd0107ca 100644 --- a/palace/drivers/eigensolver.hpp +++ b/palace/drivers/eigensolver.hpp @@ -16,6 +16,7 @@ class ErrorIndicator; class LumpedPortOperator; class Mesh; class PostOperator; +class SpaceOperator; // // Driver class for eigenmode simulations. @@ -23,19 +24,92 @@ class PostOperator; class EigenSolver : public BaseSolver { private: - void Postprocess(const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - int i, std::complex omega, double error_bkwd, double error_abs, - int num_conv, double E_elec, double E_mag, - const ErrorIndicator *indicator) const; + struct EigenPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile eig = {}; - void PostprocessEigen(int i, std::complex omega, double error_bkwd, - double error_abs, int num_conv) const; + // Print data to stdout with custom table formatting + void PrintStdoutHeader(); + void PrintStdoutRow(size_t j); - void PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int i) const; + public: + int stdout_int_print_width = 0; - void PostprocessEPR(const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - int i, std::complex omega, double E_m) const; + EigenPostPrinter() = default; + EigenPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + int n_post); + void AddMeasurement(int eigen_print_idx, std::complex omega, double error_bkwd, + double error_abs, const IoData &iodata); + }; + + class PortsPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile port_V = {}; + TableWithCSVFile port_I = {}; + + public: + PortsPostPrinter() = default; + PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, int n_expected_rows); + void AddMeasurement(int eigen_print_idx, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, const IoData &iodata); + }; + + // Common domain postprocessing for all simulation types. + class EPRPostPrinter + { + bool root_ = false; + bool do_measurement_EPR_ = false; + bool do_measurement_Q_ = false; + TableWithCSVFile port_EPR = {}; + TableWithCSVFile port_Q = {}; + + std::vector ports_with_L; + std::vector ports_with_R; + + public: + EPRPostPrinter() = default; + EPRPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, int n_expected_rows); + + void AddMeasurementEPR(double eigen_print_idx, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, double E_m, + const IoData &iodata); + void AddMeasurementQ(double eigen_print_idx, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + std::complex omega, double E_m, const IoData &iodata); + + void AddMeasurement(double eigen_print_idx, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, + std::complex omega, double E_m, const IoData &iodata); + }; + + struct PostprocessPrintResults + { + bool write_paraview_fields = false; + int n_post = 0; + + DomainsPostPrinter domains; + SurfacesPostPrinter surfaces; + ProbePostPrinter probes; + EigenPostPrinter eigen; + EPRPostPrinter epr; + + ErrorIndicatorPostPrinter error_indicator; + + PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + const PostOperator &post_op, const SpaceOperator &space_op, + int n_post_); + void PostprocessStep(const IoData &iodata, const PostOperator &post_op, + const SpaceOperator &space_op, int step, + std::complex omega, double E_elec, double E_mag, + double error_abs, double error_bkward); + void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); + }; std::pair Solve(const std::vector> &mesh) const override; diff --git a/palace/drivers/electrostaticsolver.cpp b/palace/drivers/electrostaticsolver.cpp index a57e13e8d..568c310b4 100644 --- a/palace/drivers/electrostaticsolver.cpp +++ b/palace/drivers/electrostaticsolver.cpp @@ -40,6 +40,8 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const PostOperator post_op(iodata, laplace_op, "electrostatic"); int n_step = static_cast(laplace_op.GetSources().size()); MFEM_VERIFY(n_step > 0, "No terminal boundaries specified for electrostatic simulation!"); + PostprocessPrintResults post_results(root, post_dir, post_op, + iodata.solver.electrostatic.n_post); // Right-hand side term and solution vector storage. Vector RHS(Grad.Width()), E(Grad.Height()); @@ -89,7 +91,7 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const estimator.AddErrorIndicator(E, E_elec, indicator); // Postprocess field solutions and optionally write solution to disk. - Postprocess(post_op, step, idx, E_elec, (step == n_step - 1) ? &indicator : nullptr); + post_results.PostprocessStep(iodata, post_op, step, idx, E_elec); // Next terminal. step++; @@ -99,28 +101,10 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const BlockTimer bt1(Timer::POSTPRO); SaveMetadata(ksp); PostprocessTerminals(post_op, laplace_op.GetSources(), V); + post_results.PostprocessFinal(post_op, indicator); return {indicator, laplace_op.GlobalTrueVSize()}; } -void ElectrostaticSolver::Postprocess(const PostOperator &post_op, int step, int idx, - double E_elec, const ErrorIndicator *indicator) const -{ - // The internal GridFunctions for PostOperator have already been set from the V solution - // in the main loop. - PostprocessDomains(post_op, "i", step, idx, E_elec, 0.0, 0.0, 0.0); - PostprocessSurfaces(post_op, "i", step, idx, E_elec, 0.0); - PostprocessProbes(post_op, "i", step, idx); - if (step < iodata.solver.electrostatic.n_post) - { - PostprocessFields(post_op, step, idx); - Mpi::Print(" Wrote fields to disk for terminal {:d}\n", idx); - } - if (indicator) - { - PostprocessErrorIndicator(post_op, *indicator, iodata.solver.electrostatic.n_post > 0); - } -} - void ElectrostaticSolver::PostprocessTerminals( PostOperator &post_op, const std::map> &terminal_sources, const std::vector &V) const @@ -167,6 +151,8 @@ void ElectrostaticSolver::PostprocessTerminals( { return; } + using VT = IoData::ValueType; + using fmt::format; // Write capactance matrix data. auto PrintMatrix = [&terminal_sources, this](const std::string &file, @@ -174,59 +160,93 @@ void ElectrostaticSolver::PostprocessTerminals( const std::string &unit, const mfem::DenseMatrix &mat, double scale) { - std::string path = post_dir + file; - auto output = OutputFile(path, false); - output.print("{:>{}s},", "i", table.w1); + TableWithCSVFile output(post_dir + file); + output.table.insert_column(Column("i", "i", 0, {}, {}, "")); + int j = 0; for (const auto &[idx2, data2] : terminal_sources) { - // clang-format off - output.print("{:>{}s}{}", - name + "[i][" + std::to_string(idx2) + "] " + unit, table.w, - (idx2 == terminal_sources.rbegin()->first) ? "" : ","); - // clang-format on - } - output.print("\n"); - int i = 0; - for (const auto &[idx, data] : terminal_sources) - { - int j = 0; - output.print("{:{}.{}e},", static_cast(idx), table.w1, table.p1); - for (const auto &[idx2, data2] : terminal_sources) + output.table.insert_column(format("i2{}", idx2), + format("{}[i][{}] {}", name, idx2, unit)); + // Use the fact that iterator over i and j is the same span + output.table["i"] << idx2; + + auto &col = output.table[format("i2{}", idx2)]; + for (int i = 0; i < terminal_sources.size(); i++) { - // clang-format off - output.print("{:+{}.{}e}{}", - mat(i, j) * scale, table.w, table.p, - (idx2 == terminal_sources.rbegin()->first) ? "" : ","); - // clang-format on - j++; + col << mat(i, j) * scale; } - output.print("\n"); - i++; + j++; } + output.WriteFullTableTrunc(); }; - const double F = iodata.DimensionalizeValue(IoData::ValueType::CAPACITANCE, 1.0); + const double F = iodata.DimensionalizeValue(VT::CAPACITANCE, 1.0); PrintMatrix("terminal-C.csv", "C", "(F)", C, F); PrintMatrix("terminal-Cinv.csv", "C⁻¹", "(1/F)", Cinv, 1.0 / F); PrintMatrix("terminal-Cm.csv", "C_m", "(F)", Cm, F); // Also write out a file with terminal voltage excitations. { - std::string path = post_dir + "terminal-V.csv"; - auto output = OutputFile(path, false); - // clang-format off - output.print("{:>{}s},{:>{}s}\n", - "i", table.w1, - "V_inc[i] (V)", table.w); - // clang-format on + TableWithCSVFile terminal_V(post_dir + "terminal-V.csv"); + terminal_V.table.insert_column(Column("i", "i", 0, {}, {}, "")); + terminal_V.table.insert_column("Vinc", "V_inc[i] (V)"); + int i = 0; for (const auto &[idx, data] : terminal_sources) { - // clang-format off - output.print("{:{}.{}e},{:+{}.{}e}\n", - static_cast(idx), table.w1, table.p1, - iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, 1.0), - table.w, table.p); - // clang-format on + terminal_V.table["i"] << double(idx); + terminal_V.table["Vinc"] << iodata.DimensionalizeValue(VT::VOLTAGE, 1.0); + i++; } + terminal_V.WriteFullTableTrunc(); + } +} + +ElectrostaticSolver::PostprocessPrintResults::PostprocessPrintResults( + bool root, const std::string &post_dir, const PostOperator &post_op, + int n_post_) + : n_post(n_post_), // + domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, + surfaces{true, root, post_dir, post_op, "i", n_post}, + probes{true, root, post_dir, post_op, "i", n_post}, + error_indicator{true, root, post_dir} +{ + if (n_post > 0) + { + if (post_dir.length() == 0) + { + Mpi::Warning(post_op.GetComm(), + "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " + "fields to disk in solve!\n"); + } + else + { + write_paraview_fields = true; + } + } +} + +void ElectrostaticSolver::PostprocessPrintResults::PostprocessStep( + const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_elec) +{ + domains.AddMeasurement(idx, post_op, E_elec, 0.0, 0.0, 0.0, iodata); + surfaces.AddMeasurement(idx, post_op, E_elec, 0.0, iodata); + probes.AddMeasurement(idx, post_op, iodata); + // The internal GridFunctions in PostOperator have already been set from V: + if (write_paraview_fields && step < n_post) + { + Mpi::Print("\n"); + post_op.WriteFields(step, idx); + Mpi::Print(" Wrote fields to disk for source {:d}\n", idx); + } +} + +void ElectrostaticSolver::PostprocessPrintResults::PostprocessFinal( + const PostOperator &post_op, const ErrorIndicator &indicator) +{ + BlockTimer bt0(Timer::POSTPRO); + error_indicator.PrintIndicatorStatistics(post_op, indicator); + if (write_paraview_fields) + { + post_op.WriteFieldsFinal(&indicator); } } diff --git a/palace/drivers/electrostaticsolver.hpp b/palace/drivers/electrostaticsolver.hpp index 98f9f2143..b36947141 100644 --- a/palace/drivers/electrostaticsolver.hpp +++ b/palace/drivers/electrostaticsolver.hpp @@ -31,8 +31,23 @@ class PostOperator; class ElectrostaticSolver : public BaseSolver { private: - void Postprocess(const PostOperator &post_op, int step, int idx, double E_elec, - const ErrorIndicator *indicator) const; + struct PostprocessPrintResults + { + bool write_paraview_fields = false; + int n_post = 0; + + DomainsPostPrinter domains; + SurfacesPostPrinter surfaces; + ProbePostPrinter probes; + + ErrorIndicatorPostPrinter error_indicator; + + PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + const PostOperator &post_op, int n_post_); + void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, + int idx, double E_elec); + void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); + }; void PostprocessTerminals(PostOperator &post_op, const std::map> &terminal_sources, diff --git a/palace/drivers/magnetostaticsolver.cpp b/palace/drivers/magnetostaticsolver.cpp index d8701385f..502bfd5ab 100644 --- a/palace/drivers/magnetostaticsolver.cpp +++ b/palace/drivers/magnetostaticsolver.cpp @@ -40,6 +40,8 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const int n_step = static_cast(curlcurl_op.GetSurfaceCurrentOp().Size()); MFEM_VERIFY(n_step > 0, "No surface current boundaries specified for magnetostatic simulation!"); + PostprocessPrintResults post_results(root, post_dir, post_op, + iodata.solver.magnetostatic.n_post); // Source term and solution vector storage. Vector RHS(Curl.Width()), B(Curl.Height()); @@ -92,8 +94,7 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const estimator.AddErrorIndicator(B, E_mag, indicator); // Postprocess field solutions and optionally write solution to disk. - Postprocess(post_op, step, idx, I_inc[step], E_mag, - (step == n_step - 1) ? &indicator : nullptr); + post_results.PostprocessStep(iodata, post_op, step, idx, E_mag); // Next source. step++; @@ -103,29 +104,10 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const BlockTimer bt1(Timer::POSTPRO); SaveMetadata(ksp); PostprocessTerminals(post_op, curlcurl_op.GetSurfaceCurrentOp(), A, I_inc); + post_results.PostprocessFinal(post_op, indicator); return {indicator, curlcurl_op.GlobalTrueVSize()}; } -void MagnetostaticSolver::Postprocess(const PostOperator &post_op, int step, int idx, - double I_inc, double E_mag, - const ErrorIndicator *indicator) const -{ - // The internal GridFunctions for PostOperator have already been set from the A solution - // in the main loop. - PostprocessDomains(post_op, "i", step, idx, 0.0, E_mag, 0.0, 0.0); - PostprocessSurfaces(post_op, "i", step, idx, 0.0, E_mag); - PostprocessProbes(post_op, "i", step, idx); - if (step < iodata.solver.magnetostatic.n_post) - { - PostprocessFields(post_op, step, idx); - Mpi::Print(" Wrote fields to disk for source {:d}\n", idx); - } - if (indicator) - { - PostprocessErrorIndicator(post_op, *indicator, iodata.solver.magnetostatic.n_post > 0); - } -} - void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, const SurfaceCurrentOperator &surf_j_op, const std::vector &A, @@ -175,67 +157,101 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, { return; } + using VT = IoData::ValueType; + using fmt::format; // Write inductance matrix data. auto PrintMatrix = [&surf_j_op, this](const std::string &file, const std::string &name, const std::string &unit, const mfem::DenseMatrix &mat, double scale) { - std::string path = post_dir + file; - auto output = OutputFile(path, false); - output.print("{:>{}s},", "i", table.w1); + TableWithCSVFile output(post_dir + file); + output.table.insert_column(Column("i", "i", 0, {}, {}, "")); + int j = 0; for (const auto &[idx2, data2] : surf_j_op) { - // clang-format off - output.print("{:>{}s}{}", - name + "[i][" + std::to_string(idx2) + "] " + unit, table.w, - (idx2 == surf_j_op.rbegin()->first) ? "" : ","); - // clang-format on - } - output.print("\n"); - int i = 0; - for (const auto &[idx, data] : surf_j_op) - { - int j = 0; - output.print("{:{}.{}e},", static_cast(idx), table.w1, table.p1); - for (const auto &[idx2, data2] : surf_j_op) + output.table.insert_column(format("i2{}", idx2), + format("{}[i][{}] {}", name, idx2, unit)); + // Use the fact that iterator over i and j is the same span + output.table["i"] << idx2; + + auto &col = output.table[format("i2{}", idx2)]; + for (int i = 0; i < surf_j_op.Size(); i++) { - // clang-format off - output.print("{:+{}.{}e}{}", - mat(i, j) * scale, table.w, table.p, - (idx2 == surf_j_op.rbegin()->first) ? "" : ","); - // clang-format on - j++; + col << mat(i, j) * scale; } - output.print("\n"); - i++; + j++; } + output.WriteFullTableTrunc(); }; - const double H = iodata.DimensionalizeValue(IoData::ValueType::INDUCTANCE, 1.0); + const double H = iodata.DimensionalizeValue(VT::INDUCTANCE, 1.0); PrintMatrix("terminal-M.csv", "M", "(H)", M, H); PrintMatrix("terminal-Minv.csv", "M⁻¹", "(1/H)", Minv, 1.0 / H); PrintMatrix("terminal-Mm.csv", "M_m", "(H)", Mm, H); // Also write out a file with source current excitations. { - std::string path = post_dir + "terminal-I.csv"; - auto output = OutputFile(path, false); - // clang-format off - output.print("{:>{}s},{:>{}s}\n", - "i", table.w1, - "I_inc[i] (A)", table.w); - // clang-format on + TableWithCSVFile terminal_I(post_dir + "terminal-I.csv"); + terminal_I.table.insert_column(Column("i", "i", 0, {}, {}, "")); + terminal_I.table.insert_column("Iinc", "I_inc[i] (A)"); int i = 0; for (const auto &[idx, data] : surf_j_op) { - // clang-format off - output.print("{:{}.{}e},{:+{}.{}e}\n", - static_cast(idx), table.w1, table.p1, - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_inc[i]), - table.w, table.p); - // clang-format on + terminal_I.table["i"] << double(idx); + terminal_I.table["Iinc"] << iodata.DimensionalizeValue(VT::CURRENT, I_inc[i]); i++; } + terminal_I.WriteFullTableTrunc(); + } +} + +MagnetostaticSolver::PostprocessPrintResults::PostprocessPrintResults( + bool root, const std::string &post_dir, const PostOperator &post_op, + int n_post_) + : n_post(n_post_), // + domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, + surfaces{true, root, post_dir, post_op, "i", n_post}, + probes{true, root, post_dir, post_op, "i", n_post}, + error_indicator{true, root, post_dir} +{ + if (n_post > 0) + { + if (post_dir.length() == 0) + { + Mpi::Warning(post_op.GetComm(), + "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " + "fields to disk in solve!\n"); + } + else + { + write_paraview_fields = true; + } + } +} + +void MagnetostaticSolver::PostprocessPrintResults::PostprocessStep( + const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_mag) +{ + domains.AddMeasurement(idx, post_op, 0.0, E_mag, 0.0, 0.0, iodata); + surfaces.AddMeasurement(idx, post_op, 0.0, E_mag, iodata); + probes.AddMeasurement(idx, post_op, iodata); + // The internal GridFunctions in PostOperator have already been set from A: + if (write_paraview_fields && step < n_post) + { + Mpi::Print("\n"); + post_op.WriteFields(step, idx); + Mpi::Print(" Wrote fields to disk for source {:d}\n", idx); + } +} + +void MagnetostaticSolver::PostprocessPrintResults::PostprocessFinal( + const PostOperator &post_op, const ErrorIndicator &indicator) +{ + BlockTimer bt0(Timer::POSTPRO); + error_indicator.PrintIndicatorStatistics(post_op, indicator); + if (write_paraview_fields) + { + post_op.WriteFieldsFinal(&indicator); } } diff --git a/palace/drivers/magnetostaticsolver.hpp b/palace/drivers/magnetostaticsolver.hpp index 6de3926e8..525a36a79 100644 --- a/palace/drivers/magnetostaticsolver.hpp +++ b/palace/drivers/magnetostaticsolver.hpp @@ -23,8 +23,23 @@ class SurfaceCurrentOperator; class MagnetostaticSolver : public BaseSolver { private: - void Postprocess(const PostOperator &post_op, int step, int idx, double I_inc, - double E_mag, const ErrorIndicator *indicator) const; + struct PostprocessPrintResults + { + bool write_paraview_fields = false; + int n_post = 0; + + DomainsPostPrinter domains; + SurfacesPostPrinter surfaces; + ProbePostPrinter probes; + + ErrorIndicatorPostPrinter error_indicator; + + PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + const PostOperator &post_op, int n_post_); + void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, + int idx, double E_mag); + void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); + }; void PostprocessTerminals(PostOperator &post_op, const SurfaceCurrentOperator &surf_j_op, const std::vector &A, diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index 25d1c6e98..b654d2b5d 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -38,6 +38,9 @@ TransientSolver::Solve(const std::vector> &mesh) const // Time stepping is uniform in the time domain. Index sets are for computing things like // port voltages and currents in postprocessing. PostOperator post_op(iodata, space_op, "transient"); + PostprocessPrintResults post_results(root, post_dir, post_op, space_op, n_step, + iodata.solver.transient.delta_post); + { Mpi::Print("\nComputing transient response for:\n"); bool first = true; @@ -118,21 +121,21 @@ TransientSolver::Solve(const std::vector> &mesh) const Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, E_mag * J, (E_elec + E_mag) * J); } - // Calculate and record the error indicators. Mpi::Print(" Updating solution error estimates\n"); estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); - // Postprocess port voltages/currents and optionally write solution to disk. - Postprocess(post_op, space_op.GetLumpedPortOp(), space_op.GetSurfaceCurrentOp(), step, - t, J_coef(t), E_elec, E_mag, (step == n_step - 1) ? &indicator : nullptr); - + post_results.PostprocessStep(iodata, post_op, space_op, step, t, J_coef(t), E_elec, + E_mag); + // Increment time step. step++; } + // Final postprocessing & printing BlockTimer bt1(Timer::POSTPRO); time_op.PrintStats(); SaveMetadata(time_op.GetLinearSolver()); + post_results.PostprocessFinal(post_op, indicator); return {indicator, space_op.GlobalTrueVSize()}; } @@ -245,237 +248,190 @@ int TransientSolver::GetNumSteps(double start, double end, double delta) const (delta > 0.0 && dfinal - end < delta_eps * end)); } -void TransientSolver::Postprocess(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - const SurfaceCurrentOperator &surf_j_op, int step, - double t, double J_coef, double E_elec, double E_mag, - const ErrorIndicator *indicator) const +// ----------------- +// Measurements / Postprocessing + +TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter( + bool do_measurement, bool root, const std::string &post_dir, + const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) + : root_{root}, // + do_measurement_(do_measurement // + && post_dir.length() > 0 // Valid output dir + && (surf_j_op.Size() > 0) // Needs surface currents + ) { - // The internal GridFunctions for PostOperator have already been set from the E and B - // solutions in the main time integration loop. - const double ts = iodata.DimensionalizeValue(IoData::ValueType::TIME, t); - const double E_cap = post_op.GetLumpedCapacitorEnergy(lumped_port_op); - const double E_ind = post_op.GetLumpedInductorEnergy(lumped_port_op); - PostprocessCurrents(post_op, surf_j_op, step, t, J_coef); - PostprocessPorts(post_op, lumped_port_op, step, t, J_coef); - PostprocessDomains(post_op, "t (ns)", step, ts, E_elec, E_mag, E_cap, E_ind); - PostprocessSurfaces(post_op, "t (ns)", step, ts, E_elec + E_cap, E_mag + E_ind); - PostprocessProbes(post_op, "t (ns)", step, ts); - if (iodata.solver.transient.delta_post > 0 && - step % iodata.solver.transient.delta_post == 0) + if (!do_measurement_ || !root_) { - Mpi::Print("\n"); - PostprocessFields(post_op, step / iodata.solver.transient.delta_post, ts); - Mpi::Print(" Wrote fields to disk at step {:d}\n", step); + return; } - if (indicator) + using fmt::format; + + surface_I = TableWithCSVFile(post_dir + "surface-I.csv"); + surface_I.table.reserve(n_expected_rows, surf_j_op.Size()); + surface_I.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); + for (const auto &[idx, data] : surf_j_op) { - PostprocessErrorIndicator(post_op, *indicator, iodata.solver.transient.delta_post > 0); + surface_I.table.insert_column(format("I_{}", idx), format("I_inc[{}] (A)", idx)); } + surface_I.AppendHeader(); } -namespace +void TransientSolver::CurrentsPostPrinter::AddMeasurement( + double t, double J_coef, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) { + if (!do_measurement_ || !root_) + { + return; + } + using VT = IoData::ValueType; + using fmt::format; -struct CurrentData -{ - const int idx; // Current source index - const double I_inc; // Excitation current -}; + surface_I.table["idx"] << iodata.DimensionalizeValue(VT::TIME, t); + for (const auto &[idx, data] : surf_j_op) + { + auto I_inc = data.GetExcitationCurrent() * J_coef; // I_inc(t) = g(t) I_inc + surface_I.table[format("I_{}", idx)] << iodata.DimensionalizeValue(VT::CURRENT, I_inc); + } + surface_I.AppendRow(); +} -struct PortData +TransientSolver::PortsPostPrinter::PortsPostPrinter( + bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, int n_expected_rows) + : do_measurement_{do_measurement}, root_{root} { - const int idx; // Port index - const bool excitation; // Flag for excited ports - const double V_inc, I_inc; // Incident voltage, current - const double V_i, I_i; // Port voltage, current -}; - -} // namespace + do_measurement_ = do_measurement_ // + && post_dir.length() > 0 // Valid output dir + && (lumped_port_op.Size() > 0); // Only works for lumped ports -void TransientSolver::PostprocessCurrents(const PostOperator &post_op, - const SurfaceCurrentOperator &surf_j_op, int step, - double t, double J_coef) const -{ - // Postprocess the time domain surface current excitations. - if (post_dir.length() == 0) + if (!do_measurement_ || !root_) { return; } - std::vector j_data; - j_data.reserve(surf_j_op.Size()); - for (const auto &[idx, data] : surf_j_op) - { - const double I_inc = data.GetExcitationCurrent() * J_coef; // I_inc(t) = g(t) I_inc - j_data.push_back({idx, iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_inc)}); - } - if (root && !j_data.empty()) + using fmt::format; + port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_V.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); + + port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); + port_I.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); + + for (const auto &[idx, data] : lumped_port_op) { - std::string path = post_dir + "surface-I.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) + if (data.excitation) { - output.print("{:>{}s},", "t (ns)", table.w1); - for (const auto &data : j_data) - { - // clang-format off - output.print("{:>{}s}{}", - "I_inc[" + std::to_string(data.idx) + "] (A)", table.w, - (data.idx == j_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + port_V.table.insert_column(format("inc{}", idx), format("V_inc[{}] (V)", idx)); + port_I.table.insert_column(format("inc{}", idx), format("I_inc[{}] (A)", idx)); } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::TIME, t), - table.w1, table.p1); - // clang-format on - for (const auto &data : j_data) - { - // clang-format off - output.print("{:+{}.{}e}{}", - data.I_inc, table.w, table.p, - (data.idx == j_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + port_V.table.insert_column(format("re{}", idx), format("V[{}] (V)", idx)); + port_I.table.insert_column(format("re{}", idx), format("I[{}] (A)", idx)); } + port_V.AppendHeader(); + port_I.AppendHeader(); } -void TransientSolver::PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int step, - double t, double J_coef) const +void TransientSolver::PortsPostPrinter::AddMeasurement( + double t, double J_coef, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - // Postprocess the time domain lumped port voltages and currents, which can then be used - // to compute S- or Z-parameters. - if (post_dir.length() == 0) + if (!do_measurement_ || !root_) { return; } - std::vector port_data; - port_data.reserve(lumped_port_op.Size()); + using fmt::format; + using VT = IoData::ValueType; + + // Postprocess the frequency domain lumped port voltages and currents (complex magnitude + // = sqrt(2) * RMS). + auto time = iodata.DimensionalizeValue(VT::TIME, t); + port_V.table["idx"] << time; + port_I.table["idx"] << time; + + auto unit_V = iodata.DimensionalizeValue(VT::VOLTAGE, 1.0); + auto unit_A = iodata.DimensionalizeValue(VT::CURRENT, 1.0); + for (const auto &[idx, data] : lumped_port_op) { - const double V_inc = data.GetExcitationVoltage() * J_coef; // V_inc(t) = g(t) V_inc - const double I_inc = - (std::abs(V_inc) > 0.0) ? data.GetExcitationPower() * J_coef * J_coef / V_inc : 0.0; - const double V_i = post_op.GetPortVoltage(lumped_port_op, idx).real(); - const double I_i = post_op.GetPortCurrent(lumped_port_op, idx).real(); - port_data.push_back({idx, data.excitation, - iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, V_inc), - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_inc), - iodata.DimensionalizeValue(IoData::ValueType::VOLTAGE, V_i), - iodata.DimensionalizeValue(IoData::ValueType::CURRENT, I_i)}); + if (data.excitation) + { + double V_inc = data.GetExcitationVoltage() * J_coef; // V_inc(t) = g(t) V_inc + double I_inc = (std::abs(V_inc) > 0.0) + ? data.GetExcitationPower() * J_coef * J_coef / V_inc + : 0.0; + + port_V.table[format("inc{}", idx)] << V_inc * unit_V; + port_I.table[format("inc{}", idx)] << I_inc * unit_A; + } + + std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); + std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + + port_V.table[format("re{}", idx)] << V_i.real() * unit_V; + port_I.table[format("re{}", idx)] << I_i.real() * unit_A; } - if (root && !port_data.empty()) + port_V.AppendRow(); + port_I.AppendRow(); +} + +TransientSolver::PostprocessPrintResults::PostprocessPrintResults( + bool root, const std::string &post_dir, const PostOperator &post_op, + const SpaceOperator &space_op, int n_expected_rows, int delta_post_) + : delta_post{delta_post_}, + domains{true, root, post_dir, post_op.GetDomainPostOp(), "t (ns)", n_expected_rows}, + surfaces{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, + currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, + probes{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, + ports{true, root, post_dir, space_op.GetLumpedPortOp(), n_expected_rows}, + error_indicator{true, root, post_dir} +{ + // If to print paraview fields + if (delta_post > 0) { - // Write the port voltages. + if (post_dir.length() == 0) { - std::string path = post_dir + "port-V.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", "t (ns)", table.w1); - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:>{}s},", - "V_inc[" + std::to_string(data.idx) + "] (V)", table.w); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s}{}", - "V[" + std::to_string(data.idx) + "] (V)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::TIME, t), - table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:+{}.{}e},", - data.V_inc, table.w, table.p); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e}{}", - data.V_i, table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + Mpi::Warning(post_op.GetComm(), + "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " + "fields to disk in solve!\n"); } - - // Write the port currents. + else { - std::string path = post_dir + "port-I.csv"; - auto output = OutputFile(path, (step > 0)); - if (step == 0) - { - output.print("{:>{}s},", "t (ns)", table.w1); - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:>{}s},", - "I_inc[" + std::to_string(data.idx) + "] (A)", table.w); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:>{}s}{}", - "I[" + std::to_string(data.idx) + "] (A)", table.w, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); - } - // clang-format off - output.print("{:{}.{}e},", - iodata.DimensionalizeValue(IoData::ValueType::TIME, t), - table.w1, table.p1); - // clang-format on - for (const auto &data : port_data) - { - if (data.excitation) - { - // clang-format off - output.print("{:+{}.{}e},", - data.I_inc, table.w, table.p); - // clang-format on - } - } - for (const auto &data : port_data) - { - // clang-format off - output.print("{:+{}.{}e}{}", - data.I_i, table.w, table.p, - (data.idx == port_data.back().idx) ? "" : ","); - // clang-format on - } - output.print("\n"); + write_paraview_fields = true; } } } +void TransientSolver::PostprocessPrintResults::PostprocessStep( + const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, + int step, double t, double J_coef, double E_elec, double E_mag) +{ + auto time = iodata.DimensionalizeValue(IoData::ValueType::TIME, t); + auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); + auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); + + domains.AddMeasurement(time, post_op, E_elec, E_mag, E_cap, E_ind, iodata); + surfaces.AddMeasurement(time, post_op, E_elec + E_cap, E_mag + E_ind, iodata); + currents.AddMeasurement(t, J_coef, space_op.GetSurfaceCurrentOp(), iodata); + probes.AddMeasurement(time, post_op, iodata); + ports.AddMeasurement(t, J_coef, post_op, space_op.GetLumpedPortOp(), iodata); + // The internal GridFunctions in PostOperator have already been set: + if (write_paraview_fields && (step % delta_post == 0)) + { + Mpi::Print("\n"); + post_op.WriteFields(step / delta_post, time); + Mpi::Print(" Wrote fields to disk at step {:d}\n", step + 1); + } +} + +void TransientSolver::PostprocessPrintResults::PostprocessFinal( + const PostOperator &post_op, const ErrorIndicator &indicator) +{ + BlockTimer bt0(Timer::POSTPRO); + error_indicator.PrintIndicatorStatistics(post_op, indicator); + if (write_paraview_fields) + { + post_op.WriteFieldsFinal(&indicator); + } +} + } // namespace palace diff --git a/palace/drivers/transientsolver.hpp b/palace/drivers/transientsolver.hpp index 9156a1831..2e72e69e0 100644 --- a/palace/drivers/transientsolver.hpp +++ b/palace/drivers/transientsolver.hpp @@ -17,6 +17,7 @@ class LumpedPortOperator; class Mesh; class PostOperator; class SurfaceCurrentOperator; +class SpaceOperator; // // Driver class for time-dependent driven terminal simulations. @@ -28,18 +29,56 @@ class TransientSolver : public BaseSolver int GetNumSteps(double start, double end, double delta) const; - void Postprocess(const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - const SurfaceCurrentOperator &surf_j_op, int step, double t, - double J_coef, double E_elec, double E_mag, - const ErrorIndicator *indicator) const; + class CurrentsPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile surface_I = {}; - void PostprocessCurrents(const PostOperator &post_op, - const SurfaceCurrentOperator &surf_j_op, int step, double t, - double J_coef) const; + public: + CurrentsPostPrinter() = default; + CurrentsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); + void AddMeasurement(double t, double J_coef, const SurfaceCurrentOperator &surf_j_op, + const IoData &iodata); + }; - void PostprocessPorts(const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, int step, double t, - double J_coef) const; + class PortsPostPrinter + { + bool root_ = false; + bool do_measurement_ = false; + TableWithCSVFile port_V = {}; + TableWithCSVFile port_I = {}; + + public: + PortsPostPrinter() = default; + PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + const LumpedPortOperator &lumped_port_op, int n_expected_rows); + void AddMeasurement(double t, double J_coef, const PostOperator &post_op, + const LumpedPortOperator &lumped_port_op, const IoData &iodata); + }; + + struct PostprocessPrintResults + { + bool write_paraview_fields = false; + int delta_post = 0; + + DomainsPostPrinter domains; + SurfacesPostPrinter surfaces; + CurrentsPostPrinter currents; + ProbePostPrinter probes; + PortsPostPrinter ports; + + ErrorIndicatorPostPrinter error_indicator; + + PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + const PostOperator &post_op, const SpaceOperator &space_op, + int n_expected_rows, int delta_post); + void PostprocessStep(const IoData &iodata, const PostOperator &post_op, + const SpaceOperator &space_op, int step, double t, double J_coef, + double E_elec, double E_mag); + void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); + }; std::pair Solve(const std::vector> &mesh) const override; diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 55a45bc75..10ce0cfd1 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -14,6 +14,7 @@ #include "utils/communication.hpp" #include "utils/geodata.hpp" #include "utils/iodata.hpp" +#include "utils/timer.hpp" namespace palace { @@ -714,6 +715,8 @@ void ScaleGridFunctions(double L, int dim, bool imag, T &E, T &B, T &V, T &A) void PostOperator::WriteFields(int step, double time) const { + BlockTimer bt(Timer::IO); + // Given the electric field and magnetic flux density, write the fields to disk for // visualization. Write the mesh coordinates in the same units as originally input. mfem::ParMesh &mesh = @@ -728,10 +731,14 @@ void PostOperator::WriteFields(int step, double time) const paraview_bdr.Save(); mesh::NondimensionalizeMesh(mesh, mesh_Lc0); ScaleGridFunctions(1.0 / mesh_Lc0, mesh.Dimension(), HasImag(), E, B, V, A); + + Mpi::Barrier(GetComm()); } void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const { + BlockTimer bt(Timer::IO); + // Write the mesh partitioning and (optionally) error indicators at the final step. No // need for these to be parallel objects, since the data is local to each process and // there isn't a need to ever access the element neighbors. We set the time to some @@ -803,6 +810,8 @@ void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const paraview.RegisterVCoeffField(name, gf); } mesh::NondimensionalizeMesh(mesh, mesh_Lc0); + + Mpi::Barrier(GetComm()); } std::vector> PostOperator::ProbeEField() const From d3880724c7be8070ae6e5682d1c71ea3fa6e443f Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:48 -0800 Subject: [PATCH 07/21] Clean up while loops --- palace/drivers/drivensolver.cpp | 14 ++------------ palace/drivers/transientsolver.cpp | 6 +----- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index f6f0a91ef..1c0d76dcd 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -155,10 +155,9 @@ ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator ErrorIndicator indicator; // Main frequency sweep loop. - int step = step0; double omega = omega0; auto t0 = Timer::Now(); - while (step < n_step) + for (int step = step0; step < n_step; step++, omega += delta_omega) { const double freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); Mpi::Print("\nIt {:d}/{:d}: ω/2π = {:.3e} GHz (elapsed time = {:.2e} s)\n", step + 1, @@ -204,10 +203,6 @@ ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); - - // Increment frequency. - step++; - omega += delta_omega; } // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); @@ -349,9 +344,8 @@ ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator // Main fast frequency sweep loop (online phase). Mpi::Print("\nBeginning fast frequency sweep online phase\n"); space_op.GetWavePortOp().SetSuppressOutput(false); // Disable output suppression - int step = step0; double omega = omega0; - while (step < n_step) + for (int step = step0; step < n_step; step++, omega += delta_omega) { const double freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); Mpi::Print("\nIt {:d}/{:d}: ω/2π = {:.3e} GHz (elapsed time = {:.2e} s)\n", step + 1, @@ -379,10 +373,6 @@ ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator E_mag * J, (E_elec + E_mag) * J); } post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); - - // Increment frequency. - step++; - omega += delta_omega; } // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index b654d2b5d..3506ff370 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -82,10 +82,9 @@ TransientSolver::Solve(const std::vector> &mesh) const ErrorIndicator indicator; // Main time integration loop. - int step = 0; double t = -delta_t; auto t0 = Timer::Now(); - while (step < n_step) + for (int step = 0; step < n_step; step++) { const double ts = iodata.DimensionalizeValue(IoData::ValueType::TIME, t + delta_t); Mpi::Print("\nIt {:d}/{:d}: t = {:e} ns (elapsed time = {:.2e} s)\n", step, n_step - 1, @@ -127,9 +126,6 @@ TransientSolver::Solve(const std::vector> &mesh) const post_results.PostprocessStep(iodata, post_op, space_op, step, t, J_coef(t), E_elec, E_mag); - - // Increment time step. - step++; } // Final postprocessing & printing BlockTimer bt1(Timer::POSTPRO); From 5846fa18d91e79f15362137765c3ddac63cbe5b9 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:48 -0800 Subject: [PATCH 08/21] Centralizes making output directory - Requires non-empty output dir - Canonicalize and sync among processors - Introduces palace::fs namespace to stop injecting into std --- palace/drivers/basesolver.cpp | 16 +------ palace/main.cpp | 3 ++ palace/models/postoperator.cpp | 11 ++--- palace/utils/filesystem.hpp | 11 +++-- palace/utils/geodata.cpp | 25 ++++------ palace/utils/outputdir.hpp | 83 ++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 42 deletions(-) create mode 100644 palace/utils/outputdir.hpp diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index 9cbd54af7..84cf34ce6 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -32,19 +32,13 @@ using json = nlohmann::json; namespace { -std::string GetPostDir(const std::string &output) -{ - return (output.length() > 0 && output.back() != '/') ? output + '/' : output; -} - std::string GetIterationPostDir(const std::string &output, int step, int width) { - return fmt::format("{}iteration{:0{}d}/", output, step, width); + return fs::path(output) / fmt::format("iteration{:0{}d}/", step, width); } void SaveIteration(MPI_Comm comm, const std::string &output, int step, int width) { - namespace fs = std::filesystem; BlockTimer bt(Timer::IO); Mpi::Barrier(comm); // Wait for all processes to write postprocessing files if (Mpi::Root(comm)) @@ -110,14 +104,8 @@ mfem::Array MarkedElements(const Vector &e, double threshold) BaseSolver::BaseSolver(const IoData &iodata, bool root, int size, int num_thread, const char *git_tag) - : iodata(iodata), post_dir(GetPostDir(iodata.problem.output)), root(root) + : iodata(iodata), post_dir(iodata.problem.output), root(root) { - // Create directory for output. - if (root && !std::filesystem::exists(post_dir)) - { - std::filesystem::create_directories(post_dir); - } - // Initialize simulation metadata for this simulation. if (root && post_dir.length() > 0) { diff --git a/palace/main.cpp b/palace/main.cpp index ffaf4c7fa..cf37ab71b 100644 --- a/palace/main.cpp +++ b/palace/main.cpp @@ -21,6 +21,7 @@ #include "utils/geodata.hpp" #include "utils/iodata.hpp" #include "utils/omp.hpp" +#include "utils/outputdir.hpp" #include "utils/timer.hpp" #if defined(MFEM_USE_STRUMPACK) @@ -258,7 +259,9 @@ int main(int argc, char *argv[]) // Parse configuration file. PrintPalaceBanner(world_comm); IoData iodata(argv[1], false); + MakeOutputFolder(iodata, world_comm); + BlockTimer bt1(Timer::INIT); // Initialize the MFEM device and configure libCEED backend. int omp_threads = ConfigureOmp(), ngpu = GetDeviceCount(); mfem::Device device(ConfigureDevice(iodata.solver.device), GetDeviceId(world_comm, ngpu)); diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 10ce0cfd1..b737ddec4 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -12,6 +12,7 @@ #include "models/surfacecurrentoperator.hpp" #include "models/waveportoperator.hpp" #include "utils/communication.hpp" +#include "utils/filesystem.hpp" #include "utils/geodata.hpp" #include "utils/iodata.hpp" #include "utils/timer.hpp" @@ -24,15 +25,9 @@ using namespace std::complex_literals; namespace { -auto CreateParaviewPath(const IoData &iodata, const std::string &name) +std::string CreateParaviewPath(const IoData &iodata, const std::string &name) { - std::string path = iodata.problem.output; - if (path[path.length() - 1] != '/') - { - path += '/'; - } - path += "paraview/" + name; - return path; + return fs::path(iodata.problem.output) / "paraview" / name; } } // namespace diff --git a/palace/utils/filesystem.hpp b/palace/utils/filesystem.hpp index 1c03be80b..9f86e00e9 100644 --- a/palace/utils/filesystem.hpp +++ b/palace/utils/filesystem.hpp @@ -6,12 +6,17 @@ #if defined(__cpp_lib_filesystem) || defined(__has_include) && __has_include() #include +namespace palace +{ +namespace fs = std::filesystem; +} // namespace palace #elif defined(__cpp_lib_experimental_filesystem) || \ defined(__has_include) && __has_include() -// clang-format off #include -namespace std { namespace filesystem = experimental::filesystem; } -// clang-format on +namespace palace +{ +namespace fs = std::experimental::filesystem; +} // namespace palace #else #error "Could not find system header or !" #endif diff --git a/palace/utils/geodata.cpp b/palace/utils/geodata.cpp index 56516e5c6..848a3614a 100644 --- a/palace/utils/geodata.cpp +++ b/palace/utils/geodata.cpp @@ -268,15 +268,10 @@ std::unique_ptr ReadMesh(const IoData &iodata, MPI_Comm comm) if constexpr (false) { - std::string tmp = iodata.problem.output; - if (tmp.back() != '/') + auto tmp = fs::path(iodata.problem.output) / "tmp"; + if (Mpi::Root(comm) && !fs::exists(tmp)) { - tmp += '/'; - } - tmp += "tmp/"; - if (Mpi::Root(comm) && !std::filesystem::exists(tmp)) - { - std::filesystem::create_directories(tmp); + fs::create_directories(tmp); } int width = 1 + static_cast(std::log10(Mpi::Size(comm) - 1)); std::unique_ptr gsmesh = @@ -285,7 +280,7 @@ std::unique_ptr ReadMesh(const IoData &iodata, MPI_Comm comm) mfem::ParMesh gpmesh(comm, *gsmesh, gpartitioning.get(), 0); { std::string pfile = - mfem::MakeParFilename(tmp + "part.", Mpi::Rank(comm), ".mesh", width); + mfem::MakeParFilename(tmp.string() + "part.", Mpi::Rank(comm), ".mesh", width); std::ofstream fo(pfile); // mfem::ofgzstream fo(pfile, true); // Use zlib compression if available fo.precision(MSH_FLT_PRECISION); @@ -293,7 +288,7 @@ std::unique_ptr ReadMesh(const IoData &iodata, MPI_Comm comm) } { std::string pfile = - mfem::MakeParFilename(tmp + "final.", Mpi::Rank(comm), ".mesh", width); + mfem::MakeParFilename(tmp.string() + "final.", Mpi::Rank(comm), ".mesh", width); std::ofstream fo(pfile); // mfem::ofgzstream fo(pfile, true); // Use zlib compression if available fo.precision(MSH_FLT_PRECISION); @@ -1637,12 +1632,8 @@ double RebalanceMesh(const IoData &iodata, std::unique_ptr &mesh) if (iodata.model.refinement.save_adapt_mesh) { // Create a separate serial mesh to write to disk. - std::string sfile = iodata.problem.output; - if (sfile.back() != '/') - { - sfile += '/'; - } - sfile += std::filesystem::path(iodata.model.mesh).stem().string() + ".mesh"; + auto sfile = fs::path(iodata.problem.output) / fs::path(iodata.model.mesh).stem(); + sfile += ".mesh"; auto PrintSerial = [&](mfem::Mesh &smesh) { @@ -1723,7 +1714,7 @@ std::unique_ptr LoadMesh(const std::string &mesh_file, bool remove_c // or error out if not supported. constexpr bool generate_edges = false, refine = false, fix_orientation = true; std::unique_ptr mesh; - std::filesystem::path mesh_path(mesh_file); + fs::path mesh_path(mesh_file); if (mesh_path.extension() == ".mphtxt" || mesh_path.extension() == ".mphbin" || mesh_path.extension() == ".nas" || mesh_path.extension() == ".bdf") { diff --git a/palace/utils/outputdir.hpp b/palace/utils/outputdir.hpp new file mode 100644 index 000000000..fcefa3ff5 --- /dev/null +++ b/palace/utils/outputdir.hpp @@ -0,0 +1,83 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef PALACE_UTILS_OUTPUTDIR_HPP +#define PALACE_UTILS_OUTPUTDIR_HPP + +#include +#include +#include "communication.hpp" +#include "filesystem.hpp" +#include "iodata.hpp" +#include "timer.hpp" + +namespace palace +{ + +inline void MakeOutputFolder(IoData &iodata, MPI_Comm &comm) +{ + BlockTimer bt(Timer::IO); + // Validate and make folder on root + auto root = Mpi::Root(comm); + auto &output_str = iodata.problem.output; + if (root) + { + MFEM_VERIFY(!output_str.empty(), + fmt::format("Invalid output directory, got empty string \"\".")) + // Remove any trailing "/" to get folder name + if (output_str.back() == '/') + { + output_str.erase(output_str.end() - 1); + } + // Resolve canonical path (no ".", etc) + auto output_path = fs::path(output_str); + // Make folder if it does not exist + if (!fs::exists(output_path)) + { + MFEM_VERIFY(fs::create_directories(output_path), + fmt::format("Error std::filesystem could not create a directory at {}", + output_path.string())); + } + else + { + MFEM_VERIFY(fs::is_directory(output_path), + fmt::format("Output path already exists but is not a directory: {}", + output_path.string())); + if (!fs::is_empty(output_path)) + { + Mpi::Warning("Output folder is not empty; program will overwrite content! ({})", + output_path.string()); + } + } + // Ensure we can write to folder by making test file + { + fs::path tmp_ = output_path / "tmp_test_file.txt"; + auto file_buf = fmt::output_file( + tmp_.string(), fmt::file::WRONLY | fmt::file::CREATE | fmt::file::TRUNC); + file_buf.print("Test Print"); + file_buf.close(); + MFEM_VERIFY( + fs::exists(tmp_) && fs::is_regular_file(tmp_), + fmt::format("Error creating test file in output folder: {}", tmp_.string())); + fs::remove(tmp_); + } + output_str = output_path.string(); + } + + // Broadcast new output_str to all ranks + if (Mpi::Size(comm) > 1) + { + int str_len = static_cast(output_str.size()); + if (root) + { + MFEM_VERIFY(output_str.size() == std::size_t(str_len), + "Overflow in stringbuffer size!"); + } + Mpi::Broadcast(1, &str_len, 0, comm); + output_str.resize(str_len); + Mpi::Broadcast(str_len, output_str.data(), 0, comm); + } +} + +} // namespace palace +#endif // PALACE_UTILS_OUTPUTDIR_HPP \ No newline at end of file From eadd7459a8f77ad9cb394850cb864631eeaee726 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:49 -0800 Subject: [PATCH 09/21] Change post_dir to fs::path - Simplify checks since output directory always exists --- palace/drivers/basesolver.cpp | 74 +++++++++----------------- palace/drivers/basesolver.hpp | 11 ++-- palace/drivers/drivensolver.cpp | 64 +++++++++------------- palace/drivers/drivensolver.hpp | 8 +-- palace/drivers/eigensolver.cpp | 52 ++++++------------ palace/drivers/eigensolver.hpp | 9 ++-- palace/drivers/electrostaticsolver.cpp | 24 ++------- palace/drivers/electrostaticsolver.hpp | 2 +- palace/drivers/magnetostaticsolver.cpp | 24 ++------- palace/drivers/magnetostaticsolver.hpp | 2 +- palace/drivers/transientsolver.cpp | 38 ++++--------- palace/drivers/transientsolver.hpp | 6 +-- 12 files changed, 106 insertions(+), 208 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index 84cf34ce6..1a3566ad6 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -32,40 +32,35 @@ using json = nlohmann::json; namespace { -std::string GetIterationPostDir(const std::string &output, int step, int width) -{ - return fs::path(output) / fmt::format("iteration{:0{}d}/", step, width); -} - -void SaveIteration(MPI_Comm comm, const std::string &output, int step, int width) +void SaveIteration(MPI_Comm comm, const fs::path &output_dir, int step, int width) { BlockTimer bt(Timer::IO); Mpi::Barrier(comm); // Wait for all processes to write postprocessing files if (Mpi::Root(comm)) { // Create a subfolder for the results of this adaptation. - const std::string step_output = GetIterationPostDir(output, step, width); + auto step_output = output_dir / fmt::format("iteration{:0{}d}", step, width); if (!fs::exists(step_output)) { fs::create_directories(step_output); } constexpr auto options = fs::copy_options::recursive | fs::copy_options::overwrite_existing; - for (const auto &f : fs::directory_iterator(output)) + for (const auto &f : fs::directory_iterator(output_dir)) { if (f.path().filename().string().rfind("iteration") == 0) { continue; } - fs::copy(f, step_output + f.path().filename().string(), options); + fs::copy(f, step_output / f.path().filename(), options); } } Mpi::Barrier(comm); } -json LoadMetadata(const std::string &post_dir) +json LoadMetadata(const fs::path &post_dir) { - std::string path = post_dir + "palace.json"; + std::string path = post_dir / "palace.json"; std::ifstream fi(path); if (!fi.is_open()) { @@ -74,9 +69,9 @@ json LoadMetadata(const std::string &post_dir) return json::parse(fi); } -void WriteMetadata(const std::string &post_dir, const json &meta) +void WriteMetadata(const fs::path &post_dir, const json &meta) { - std::string path = post_dir + "palace.json"; + std::string path = post_dir / "palace.json"; std::ofstream fo(path); if (!fo.is_open()) { @@ -107,7 +102,7 @@ BaseSolver::BaseSolver(const IoData &iodata, bool root, int size, int num_thread : iodata(iodata), post_dir(iodata.problem.output), root(root) { // Initialize simulation metadata for this simulation. - if (root && post_dir.length() > 0) + if (root) { json meta; if (git_tag) @@ -246,10 +241,6 @@ void BaseSolver::SolveEstimateMarkRefine(std::vector> &mes void BaseSolver::SaveMetadata(const FiniteElementSpaceHierarchy &fespaces) const { - if (post_dir.length() == 0) - { - return; - } const auto &fespace = fespaces.GetFinestFESpace(); HYPRE_BigInt ne = fespace.GetParMesh().GetNE(); Mpi::GlobalSum(1, &ne, fespace.GetComm()); @@ -271,10 +262,6 @@ void BaseSolver::SaveMetadata(const FiniteElementSpaceHierarchy &fespaces) const template void BaseSolver::SaveMetadata(const SolverType &ksp) const { - if (post_dir.length() == 0) - { - return; - } if (root) { json meta = LoadMetadata(post_dir); @@ -286,10 +273,6 @@ void BaseSolver::SaveMetadata(const SolverType &ksp) const void BaseSolver::SaveMetadata(const Timer &timer) const { - if (post_dir.length() == 0) - { - return; - } if (root) { json meta = LoadMetadata(post_dir); @@ -337,22 +320,19 @@ struct ProbeData } // namespace BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const DomainPostOperator &dom_post_op, const std::string &idx_col_name, int n_expected_rows) : do_measurement_{do_measurement}, root_{root} { - do_measurement_ = do_measurement_ // - && post_dir.length() > 0; // Valid output dir - if (!do_measurement_ || !root_) { return; } using fmt::format; - domain_E = TableWithCSVFile(post_dir + "domain-E.csv"); + domain_E = TableWithCSVFile(post_dir / "domain-E.csv"); domain_E.table.reserve(n_expected_rows, 4 + dom_post_op.M_i.size()); domain_E.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); @@ -421,17 +401,15 @@ void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionfu } BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) : root_{root}, - do_measurement_flux_(do_measurement // - && post_dir.length() > 0 // Valid output dir + do_measurement_flux_(do_measurement // && post_op.GetSurfacePostOp().flux_surfs.size() > 0 // Has flux ), - do_measurement_eps_(do_measurement // - && post_dir.length() > 0 // Valid output dir + do_measurement_eps_(do_measurement // && post_op.GetSurfacePostOp().eps_surfs.size() > 0 // Has eps ) { @@ -443,7 +421,7 @@ BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool r if (do_measurement_flux_) { - surface_F = TableWithCSVFile(post_dir + "surface-F.csv"); + surface_F = TableWithCSVFile(post_dir / "surface-F.csv"); surface_F.table.reserve(n_expected_rows, 2 * post_op.GetSurfacePostOp().flux_surfs.size() + 1); surface_F.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); @@ -492,7 +470,7 @@ BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool r if (do_measurement_eps_) { - surface_Q = TableWithCSVFile(post_dir + "surface-Q.csv"); + surface_Q = TableWithCSVFile(post_dir / "surface-Q.csv"); surface_Q.table.reserve(n_expected_rows, 2 * post_op.GetSurfacePostOp().eps_surfs.size() + 1); surface_Q.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); @@ -610,7 +588,7 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurement(double idx_value_dimensionf } BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) @@ -619,12 +597,10 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, { #if defined(MFEM_USE_GSLIB) do_measurement_E_ = do_measurement_E_ // - && (post_dir.length() > 0) // Valid output dir && (post_op.GetProbes().size() > 0) // Has probes defined && post_op.HasE(); // Has E fields do_measurement_B_ = do_measurement_B_ // - && (post_dir.length() > 0) // Valid output dir && (post_op.GetProbes().size() > 0) // Has probes defined && post_op.HasB(); // Has B fields @@ -652,7 +628,7 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, if (do_measurement_E_) { - probe_E = TableWithCSVFile(post_dir + "probe-E.csv"); + probe_E = TableWithCSVFile(post_dir / "probe-E.csv"); probe_E.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); probe_E.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); @@ -681,7 +657,7 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, if (do_measurement_B_) { - probe_B = TableWithCSVFile(post_dir + "probe-B.csv"); + probe_B = TableWithCSVFile(post_dir / "probe-B.csv"); probe_B.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); probe_B.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); @@ -800,19 +776,17 @@ void BaseSolver::ProbePostPrinter::AddMeasurement(double idx_value_dimensionful, #endif } -BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter( - bool do_measurement, bool root, const std::string &post_dir) - : root_{root}, do_measurement_{ - do_measurement // - && post_dir.length() > 0 // Valid output dir - } +BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(bool do_measurement, + bool root, + const fs::path &post_dir) + : root_{root}, do_measurement_{do_measurement} { if (!do_measurement_ || !root_) { return; } - error_indicator = TableWithCSVFile(post_dir + "error-indicators.csv"); + error_indicator = TableWithCSVFile(post_dir / "error-indicators.csv"); error_indicator.table.reserve(1, 4); error_indicator.table.insert_column("norm", "Norm"); diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index 7c2082bd2..f425789bb 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -8,6 +8,7 @@ #include #include #include +#include "utils/filesystem.hpp" #include "utils/tablecsv.hpp" namespace palace @@ -31,7 +32,7 @@ class BaseSolver const IoData &iodata; // Parameters for writing postprocessing outputs. - std::string post_dir; + fs::path post_dir; bool root; // Common domain postprocessing for all simulation types. @@ -43,7 +44,7 @@ class BaseSolver public: DomainsPostPrinter() = default; - DomainsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + DomainsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const DomainPostOperator &dom_post_op, const std::string &idx_col_name, int n_expected_rows); void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, @@ -62,7 +63,7 @@ class BaseSolver public: SurfacesPostPrinter() = default; - SurfacesPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + SurfacesPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, @@ -87,7 +88,7 @@ class BaseSolver public: ProbePostPrinter() = default; - ProbePostPrinter(bool do_measurement, bool root, const std::string &post_dir, + ProbePostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); @@ -110,7 +111,7 @@ class BaseSolver public: ErrorIndicatorPostPrinter() = default; - ErrorIndicatorPostPrinter(bool do_measurement, bool root, const std::string &post_dir); + ErrorIndicatorPostPrinter(bool do_measurement, bool root, const fs::path &post_dir); void PrintIndicatorStatistics(const PostOperator &post_op, const ErrorIndicator &indicator); diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index 1c0d76dcd..5d80de0f4 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -400,19 +400,18 @@ int DrivenSolver::GetNumSteps(double start, double end, double delta) const // Measurements / Postprocessing DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( - bool do_measurement, bool root, const std::string &post_dir, + bool do_measurement, bool root, const fs::path &post_dir, const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root} + : root_{root}, do_measurement_{ + do_measurement // + && (surf_j_op.Size() > 0) // Needs surface currents + } { - do_measurement_ = do_measurement_ // - && post_dir.length() > 0 // Valid output dir - && (surf_j_op.Size() > 0); // Needs surface currents - if (!do_measurement_ || !root_) { return; } - surface_I = TableWithCSVFile(post_dir + "surface-I.csv"); + surface_I = TableWithCSVFile(post_dir / "surface-I.csv"); surface_I.table.reserve(n_expected_rows, surf_j_op.Size()); surface_I.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); for (const auto &[idx, data] : surf_j_op) @@ -443,25 +442,24 @@ void DrivenSolver::CurrentsPostPrinter::AddMeasurement( } DrivenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root} + : root_{root}, do_measurement_{ + do_measurement // + && (lumped_port_op.Size() > 0) // Only works for lumped ports + } { - do_measurement_ = do_measurement_ // - && post_dir.length() > 0 // Valid output dir - && (lumped_port_op.Size() > 0); // Only works for lumped ports - if (!do_measurement_ || !root_) { return; } using fmt::format; - port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V = TableWithCSVFile(post_dir / "port-V.csv"); port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); port_V.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); - port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I = TableWithCSVFile(post_dir / "port-I.csv"); port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); port_I.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); @@ -527,16 +525,18 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( } DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( - bool do_measurement, bool root, const std::string &post_dir, + bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root}, src_lumped_port{lumped_port_op.Size() > 0} + : root_{root}, + do_measurement_{ + do_measurement // + && ((lumped_port_op.Size() > 0) xor + (wave_port_op.Size() > 0)) // either lumped or wave but not both + + }, + src_lumped_port{lumped_port_op.Size() > 0} { - do_measurement_ = do_measurement_ // - && (post_dir.length() > 0) // valid output dir - && (src_lumped_port xor - (wave_port_op.Size() > 0)); // either lumped or wave but not both - if (!do_measurement_ || !root_) { return; @@ -567,7 +567,7 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( } using fmt::format; - port_S = TableWithCSVFile(post_dir + "port-S.csv"); + port_S = TableWithCSVFile(post_dir / "port-S.csv"); port_S.table.reserve(n_expected_rows, lumped_port_op.Size()); port_S.table.insert_column(Column("idx", "f (GHz)", 0, {}, {}, "")); @@ -639,9 +639,9 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( } DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const std::string &post_dir, const PostOperator &post_op, + bool root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post_) - : delta_post{delta_post_}, + : delta_post{delta_post_}, write_paraview_fields{delta_post_ > 0}, domains{true, root, post_dir, post_op.GetDomainPostOp(), "f (GHz)", n_expected_rows}, surfaces{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, @@ -655,20 +655,6 @@ DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( n_expected_rows}, error_indicator{true, root, post_dir} { - // If to print paraview fields - if (delta_post > 0) - { - if (post_dir.length() == 0) - { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk in solve!\n"); - } - else - { - write_paraview_fields = true; - } - } } void DrivenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata, diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index 4f815a1d6..cc1eb8208 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -38,7 +38,7 @@ class DrivenSolver : public BaseSolver public: CurrentsPostPrinter() = default; - CurrentsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + CurrentsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); void AddMeasurement(double omega, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata); @@ -53,7 +53,7 @@ class DrivenSolver : public BaseSolver public: PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows); void AddMeasurement(double omega, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); @@ -76,7 +76,7 @@ class DrivenSolver : public BaseSolver public: SParametersPostPrinter() = default; - SParametersPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + SParametersPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, int n_expected_rows); void AddMeasurement(double omega, const PostOperator &post_op, @@ -98,7 +98,7 @@ class DrivenSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index a56faddfa..ca881e60a 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -419,10 +419,8 @@ void EigenSolver::EigenPostPrinter::PrintStdoutRow(size_t j) } EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, int n_eig) - : root_{root}, do_measurement_(do_measurement // - && post_dir.length() > 0 // Valid output dir - ), + const fs::path &post_dir, int n_eig) + : root_{root}, do_measurement_(do_measurement), stdout_int_print_width(1 + static_cast(std::log10(n_eig))) { // Note: we switch to n_eig rather than n_conv for padding since we don't know n_conv @@ -431,7 +429,7 @@ EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, { return; } - eig = TableWithCSVFile(post_dir + "eig.csv"); + eig = TableWithCSVFile(post_dir / "eig.csv"); eig.table.reserve(n_eig, 6); eig.table.insert_column(Column("idx", "m", 0, {}, {}, "")); eig.table.insert_column("f_re", "Re{f} (GHz)"); @@ -469,25 +467,24 @@ void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, } EigenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root} + : root_{root}, do_measurement_{ + do_measurement // + && (lumped_port_op.Size() > 0) // + } { - do_measurement_ = do_measurement_ // - && post_dir.length() > 0 // Valid output dir - && (lumped_port_op.Size() > 0); // Only works for lumped ports - if (!do_measurement_ || !root_) { return; } using fmt::format; - port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V = TableWithCSVFile(post_dir / "port-V.csv"); port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); port_V.table.insert_column(Column("idx", "m", 0, {}, {}, "")); - port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I = TableWithCSVFile(post_dir / "port-I.csv"); port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); port_I.table.insert_column(Column("idx", "m", 0, {}, {}, "")); @@ -538,13 +535,11 @@ void EigenSolver::PortsPostPrinter::AddMeasurement(int eigen_print_idx, } EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, - const std::string &post_dir, + const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows) - : root_{root}, // - do_measurement_EPR_(do_measurement // - && post_dir.length() > 0 // Valid output dir - && lumped_port_op.Size() > 0), + : root_{root}, do_measurement_EPR_(do_measurement // + && lumped_port_op.Size() > 0), do_measurement_Q_(do_measurement_EPR_) { // Mode EPR for lumped inductor elements: @@ -572,7 +567,7 @@ EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, if (do_measurement_EPR_) { - port_EPR = TableWithCSVFile(post_dir + "port-EPR.csv"); + port_EPR = TableWithCSVFile(post_dir / "port-EPR.csv"); port_EPR.table.reserve(n_expected_rows, 1 + ports_with_L.size()); port_EPR.table.insert_column(Column("idx", "m", 0, {}, {}, "")); for (const auto idx : ports_with_L) @@ -583,7 +578,7 @@ EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, } if (do_measurement_Q_) { - port_Q = TableWithCSVFile(post_dir + "port-Q.csv"); + port_Q = TableWithCSVFile(post_dir / "port-Q.csv"); port_Q.table.reserve(n_expected_rows, 1 + ports_with_R.size()); port_Q.table.insert_column(Column("idx", "m", 0, {}, {}, "")); for (const auto idx : ports_with_L) @@ -677,30 +672,17 @@ void EigenSolver::EPRPostPrinter::AddMeasurement(double eigen_print_idx, } EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, - const std::string &post_dir, + const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_post_) - : n_post(n_post_), // + : n_post(n_post_), write_paraview_fields(n_post_ > 0), domains{true, root, post_dir, post_op.GetDomainPostOp(), "m", n_post}, surfaces{true, root, post_dir, post_op, "m", n_post}, probes{true, root, post_dir, post_op, "m", n_post}, eigen{true, root, post_dir, n_post}, epr{true, root, post_dir, space_op.GetLumpedPortOp(), n_post}, error_indicator{true, root, post_dir} { - if (n_post > 0) - { - if (post_dir.length() == 0) - { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk in solve!\n"); - } - else - { - write_paraview_fields = true; - } - } } void EigenSolver::PostprocessPrintResults::PostprocessStep( diff --git a/palace/drivers/eigensolver.hpp b/palace/drivers/eigensolver.hpp index 0fd0107ca..4fe4623c6 100644 --- a/palace/drivers/eigensolver.hpp +++ b/palace/drivers/eigensolver.hpp @@ -38,8 +38,7 @@ class EigenSolver : public BaseSolver int stdout_int_print_width = 0; EigenPostPrinter() = default; - EigenPostPrinter(bool do_measurement, bool root, const std::string &post_dir, - int n_post); + EigenPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, int n_post); void AddMeasurement(int eigen_print_idx, std::complex omega, double error_bkwd, double error_abs, const IoData &iodata); }; @@ -53,7 +52,7 @@ class EigenSolver : public BaseSolver public: PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows); void AddMeasurement(int eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); @@ -73,7 +72,7 @@ class EigenSolver : public BaseSolver public: EPRPostPrinter() = default; - EPRPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + EPRPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows); void AddMeasurementEPR(double eigen_print_idx, const PostOperator &post_op, @@ -101,7 +100,7 @@ class EigenSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, diff --git a/palace/drivers/electrostaticsolver.cpp b/palace/drivers/electrostaticsolver.cpp index 568c310b4..412e97287 100644 --- a/palace/drivers/electrostaticsolver.cpp +++ b/palace/drivers/electrostaticsolver.cpp @@ -147,7 +147,7 @@ void ElectrostaticSolver::PostprocessTerminals( Cinv.Invert(); // In-place, uses LAPACK (when available) and should be cheap // Only root writes to disk (every process has full matrices). - if (!root || post_dir.length() == 0) + if (!root) { return; } @@ -160,7 +160,7 @@ void ElectrostaticSolver::PostprocessTerminals( const std::string &unit, const mfem::DenseMatrix &mat, double scale) { - TableWithCSVFile output(post_dir + file); + TableWithCSVFile output(post_dir / file); output.table.insert_column(Column("i", "i", 0, {}, {}, "")); int j = 0; for (const auto &[idx2, data2] : terminal_sources) @@ -186,7 +186,7 @@ void ElectrostaticSolver::PostprocessTerminals( // Also write out a file with terminal voltage excitations. { - TableWithCSVFile terminal_V(post_dir + "terminal-V.csv"); + TableWithCSVFile terminal_V(post_dir / "terminal-V.csv"); terminal_V.table.insert_column(Column("i", "i", 0, {}, {}, "")); terminal_V.table.insert_column("Vinc", "V_inc[i] (V)"); int i = 0; @@ -201,27 +201,13 @@ void ElectrostaticSolver::PostprocessTerminals( } ElectrostaticSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const std::string &post_dir, const PostOperator &post_op, - int n_post_) - : n_post(n_post_), // + bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) + : n_post(n_post_), write_paraview_fields(n_post_ > 0), domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, surfaces{true, root, post_dir, post_op, "i", n_post}, probes{true, root, post_dir, post_op, "i", n_post}, error_indicator{true, root, post_dir} { - if (n_post > 0) - { - if (post_dir.length() == 0) - { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk in solve!\n"); - } - else - { - write_paraview_fields = true; - } - } } void ElectrostaticSolver::PostprocessPrintResults::PostprocessStep( diff --git a/palace/drivers/electrostaticsolver.hpp b/palace/drivers/electrostaticsolver.hpp index b36947141..0a71e8fbd 100644 --- a/palace/drivers/electrostaticsolver.hpp +++ b/palace/drivers/electrostaticsolver.hpp @@ -42,7 +42,7 @@ class ElectrostaticSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_elec); diff --git a/palace/drivers/magnetostaticsolver.cpp b/palace/drivers/magnetostaticsolver.cpp index 502bfd5ab..d5fac30f9 100644 --- a/palace/drivers/magnetostaticsolver.cpp +++ b/palace/drivers/magnetostaticsolver.cpp @@ -153,7 +153,7 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, Minv.Invert(); // In-place, uses LAPACK (when available) and should be cheap // Only root writes to disk (every process has full matrices). - if (!root || post_dir.length() == 0) + if (!root) { return; } @@ -165,7 +165,7 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, const std::string &unit, const mfem::DenseMatrix &mat, double scale) { - TableWithCSVFile output(post_dir + file); + TableWithCSVFile output(post_dir / file); output.table.insert_column(Column("i", "i", 0, {}, {}, "")); int j = 0; for (const auto &[idx2, data2] : surf_j_op) @@ -191,7 +191,7 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, // Also write out a file with source current excitations. { - TableWithCSVFile terminal_I(post_dir + "terminal-I.csv"); + TableWithCSVFile terminal_I(post_dir / "terminal-I.csv"); terminal_I.table.insert_column(Column("i", "i", 0, {}, {}, "")); terminal_I.table.insert_column("Iinc", "I_inc[i] (A)"); int i = 0; @@ -206,27 +206,13 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, } MagnetostaticSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const std::string &post_dir, const PostOperator &post_op, - int n_post_) - : n_post(n_post_), // + bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) + : n_post(n_post_), write_paraview_fields(n_post_ > 0), domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, surfaces{true, root, post_dir, post_op, "i", n_post}, probes{true, root, post_dir, post_op, "i", n_post}, error_indicator{true, root, post_dir} { - if (n_post > 0) - { - if (post_dir.length() == 0) - { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk in solve!\n"); - } - else - { - write_paraview_fields = true; - } - } } void MagnetostaticSolver::PostprocessPrintResults::PostprocessStep( diff --git a/palace/drivers/magnetostaticsolver.hpp b/palace/drivers/magnetostaticsolver.hpp index 525a36a79..a95cf91ef 100644 --- a/palace/drivers/magnetostaticsolver.hpp +++ b/palace/drivers/magnetostaticsolver.hpp @@ -34,7 +34,7 @@ class MagnetostaticSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_mag); diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index 3506ff370..20ddb8b45 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -248,11 +248,10 @@ int TransientSolver::GetNumSteps(double start, double end, double delta) const // Measurements / Postprocessing TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter( - bool do_measurement, bool root, const std::string &post_dir, + bool do_measurement, bool root, const fs::path &post_dir, const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) : root_{root}, // do_measurement_(do_measurement // - && post_dir.length() > 0 // Valid output dir && (surf_j_op.Size() > 0) // Needs surface currents ) { @@ -262,7 +261,7 @@ TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter( } using fmt::format; - surface_I = TableWithCSVFile(post_dir + "surface-I.csv"); + surface_I = TableWithCSVFile(post_dir / "surface-I.csv"); surface_I.table.reserve(n_expected_rows, surf_j_op.Size()); surface_I.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); for (const auto &[idx, data] : surf_j_op) @@ -292,24 +291,23 @@ void TransientSolver::CurrentsPostPrinter::AddMeasurement( } TransientSolver::PortsPostPrinter::PortsPostPrinter( - bool do_measurement, bool root, const std::string &post_dir, + bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root} + : root_{root}, do_measurement_{ + do_measurement // + && (lumped_port_op.Size() > 0) // Only works for lumped ports + } { - do_measurement_ = do_measurement_ // - && post_dir.length() > 0 // Valid output dir - && (lumped_port_op.Size() > 0); // Only works for lumped ports - if (!do_measurement_ || !root_) { return; } using fmt::format; - port_V = TableWithCSVFile(post_dir + "port-V.csv"); + port_V = TableWithCSVFile(post_dir / "port-V.csv"); port_V.table.reserve(n_expected_rows, lumped_port_op.Size()); port_V.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); - port_I = TableWithCSVFile(post_dir + "port-I.csv"); + port_I = TableWithCSVFile(post_dir / "port-I.csv"); port_I.table.reserve(n_expected_rows, lumped_port_op.Size()); port_I.table.insert_column(Column("idx", "t (ns)", 0, {}, {}, "")); @@ -371,9 +369,9 @@ void TransientSolver::PortsPostPrinter::AddMeasurement( } TransientSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const std::string &post_dir, const PostOperator &post_op, + bool root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post_) - : delta_post{delta_post_}, + : delta_post{delta_post_}, write_paraview_fields(delta_post_ > 0), domains{true, root, post_dir, post_op.GetDomainPostOp(), "t (ns)", n_expected_rows}, surfaces{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, @@ -381,20 +379,6 @@ TransientSolver::PostprocessPrintResults::PostprocessPrintResults( ports{true, root, post_dir, space_op.GetLumpedPortOp(), n_expected_rows}, error_indicator{true, root, post_dir} { - // If to print paraview fields - if (delta_post > 0) - { - if (post_dir.length() == 0) - { - Mpi::Warning(post_op.GetComm(), - "No file specified under [\"Problem\"][\"Output\"]!\nSkipping saving of " - "fields to disk in solve!\n"); - } - else - { - write_paraview_fields = true; - } - } } void TransientSolver::PostprocessPrintResults::PostprocessStep( diff --git a/palace/drivers/transientsolver.hpp b/palace/drivers/transientsolver.hpp index 2e72e69e0..ab2413c70 100644 --- a/palace/drivers/transientsolver.hpp +++ b/palace/drivers/transientsolver.hpp @@ -37,7 +37,7 @@ class TransientSolver : public BaseSolver public: CurrentsPostPrinter() = default; - CurrentsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + CurrentsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); void AddMeasurement(double t, double J_coef, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata); @@ -52,7 +52,7 @@ class TransientSolver : public BaseSolver public: PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const std::string &post_dir, + PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows); void AddMeasurement(double t, double J_coef, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); @@ -71,7 +71,7 @@ class TransientSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const std::string &post_dir, + PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, From 7413f13620da70889a06b45545f59109a949f86e Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:49 -0800 Subject: [PATCH 10/21] Rework PostOperator - Cleaner separation between measurement (PostOperator) and printing (in solvers) - Treat frequency as measurement to be supplied by solver - Add caching system in PostOperator for all measurements, extending port case - Simplify energy measurements using caching system - Store pointer view to lumped/wave port operators in PostOperator where appropriate --- palace/drivers/basesolver.cpp | 187 ++----- palace/drivers/basesolver.hpp | 15 +- palace/drivers/drivensolver.cpp | 88 ++-- palace/drivers/drivensolver.hpp | 9 +- palace/drivers/eigensolver.cpp | 131 ++--- palace/drivers/eigensolver.hpp | 16 +- palace/drivers/electrostaticsolver.cpp | 14 +- palace/drivers/electrostaticsolver.hpp | 2 +- palace/drivers/magnetostaticsolver.cpp | 14 +- palace/drivers/magnetostaticsolver.hpp | 2 +- palace/drivers/transientsolver.cpp | 23 +- palace/drivers/transientsolver.hpp | 3 +- palace/fem/errorindicator.hpp | 13 + palace/fem/interpolator.cpp | 2 +- palace/models/postoperator.cpp | 669 ++++++++++++++++++------- palace/models/postoperator.hpp | 135 +++-- 16 files changed, 779 insertions(+), 544 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index 1a3566ad6..b045c774b 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -287,41 +287,9 @@ void BaseSolver::SaveMetadata(const Timer &timer) const } } -namespace -{ - -struct EnergyData -{ - const int idx; // Domain index - const double E_elec; // Electric field energy - const double E_mag; // Magnetic field energy -}; - -struct FluxData -{ - const int idx; // Surface index - const std::complex Phi; // Integrated flux - const SurfaceFluxType type; // Flux type -}; - -struct EpsData -{ - const int idx; // Interface index - const double p; // Participation ratio - const double Q; // Quality factor -}; - -struct ProbeData -{ - const int idx; // Probe index - const std::complex Fx, Fy, Fz; // Field values at probe location -}; - -} // namespace - BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const DomainPostOperator &dom_post_op, + const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) : do_measurement_{do_measurement}, root_{root} @@ -333,7 +301,7 @@ BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool roo using fmt::format; domain_E = TableWithCSVFile(post_dir / "domain-E.csv"); - domain_E.table.reserve(n_expected_rows, 4 + dom_post_op.M_i.size()); + domain_E.table.reserve(n_expected_rows, 4 + post_op.GetDomainPostOp().M_i.size()); domain_E.table.insert_column(Column("idx", idx_col_name, 0, {}, {}, "")); domain_E.table.insert_column("Ee", "E_elec (J)"); @@ -341,7 +309,7 @@ BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool roo domain_E.table.insert_column("Ec", "E_cap (J)"); domain_E.table.insert_column("Ei", "E_ind (J)"); - for (const auto &[idx, data] : dom_post_op.M_i) + for (const auto &[idx, data] : post_op.GetDomainPostOp().M_i) { domain_E.table.insert_column(format("Ee{}", idx), format("E_elec[{}] (J)", idx)); domain_E.table.insert_column(format("pe{}", idx), format("p_elec[{}]", idx)); @@ -353,47 +321,36 @@ BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool roo void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, - double E_cap, double E_ind, const IoData &iodata) { - if (!do_measurement_) - { - return; - } - - // MPI Gather - std::vector energy_data; - energy_data.reserve(post_op.GetDomainPostOp().M_i.size()); - for (const auto &[idx, data] : post_op.GetDomainPostOp().M_i) - { - double E_elec_i = (E_elec > 0.0) ? post_op.GetEFieldEnergy(idx) : 0.0; - double E_mag_i = (E_mag > 0.0) ? post_op.GetHFieldEnergy(idx) : 0.0; - energy_data.emplace_back(EnergyData{idx, E_elec_i, E_mag_i}); - } - - if (!root_) + if (!do_measurement_ || !root_) { return; } - using VT = IoData::ValueType; using fmt::format; + double oneJ = iodata.DimensionalizeValue(VT::ENERGY, 1.0); + domain_E.table["idx"] << idx_value_dimensionful; - domain_E.table["Ee"] << iodata.DimensionalizeValue(VT::ENERGY, E_elec); - domain_E.table["Em"] << iodata.DimensionalizeValue(VT::ENERGY, E_mag); - domain_E.table["Ec"] << iodata.DimensionalizeValue(VT::ENERGY, E_cap); - domain_E.table["Ei"] << iodata.DimensionalizeValue(VT::ENERGY, E_ind); + double E_elec = post_op.GetEFieldEnergy(); + double E_mag = post_op.GetHFieldEnergy(); + + domain_E.table["Ee"] << E_elec * oneJ; + domain_E.table["Em"] << E_mag * oneJ; + domain_E.table["Ec"] << post_op.GetLumpedCapacitorEnergy() * oneJ; + domain_E.table["Ei"] << post_op.GetLumpedInductorEnergy() * oneJ; // Write the field and lumped element energies. - for (const auto &[idx, E_e, E_m] : energy_data) + for (const auto &[idx, data] : post_op.GetDomainPostOp().M_i) { - domain_E.table[format("Ee{}", idx)] << iodata.DimensionalizeValue(VT::ENERGY, E_e); + double E_e = post_op.GetEFieldEnergy(idx); + double E_m = post_op.GetHFieldEnergy(idx); + domain_E.table[format("Ee{}", idx)] << E_e * oneJ; domain_E.table[format("pe{}", idx)] << ((std::abs(E_elec) > 0.0) ? (E_e / E_elec) : 0.0); - domain_E.table[format("Em{}", idx)] << iodata.DimensionalizeValue(VT::ENERGY, E_m); + domain_E.table[format("Em{}", idx)] << E_m * oneJ; domain_E.table[format("pm{}", idx)] << ((std::abs(E_mag) > 0.0) ? E_m / E_mag : 0.0); } @@ -485,24 +442,20 @@ BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool r void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata) { - if (!do_measurement_flux_) + if (!do_measurement_flux_ || !root_) { return; } using VT = IoData::ValueType; using fmt::format; - // Gather const bool has_imaginary = post_op.HasImag(); - std::vector flux_data; - flux_data.reserve(post_op.GetSurfacePostOp().flux_surfs.size()); - for (const auto &[idx, data] : post_op.GetSurfacePostOp().flux_surfs) + auto flux_data_vec = post_op.GetSurfaceFluxAll(); + auto dimensionlize_flux = [&iodata](auto Phi, SurfaceFluxType flux_type) { - auto Phi = post_op.GetSurfaceFlux(idx); - switch (data.type) + switch (flux_type) { case SurfaceFluxType::ELECTRIC: Phi *= iodata.DimensionalizeValue(VT::CAPACITANCE, 1.0); @@ -516,23 +469,17 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimens Phi *= iodata.DimensionalizeValue(VT::POWER, 1.0); break; } - flux_data.emplace_back(FluxData{idx, Phi, data.type}); - } - - if (!root_) - { - return; - } - + return Phi; + }; surface_F.table["idx"] << idx_value_dimensionful; - - for (const auto &[idx, Phi, data_type] : flux_data) + for (const auto &flux_data : flux_data_vec) { - surface_F.table[format("F_{}_re", idx)] << Phi.real(); - if (has_imaginary && - (data_type == SurfaceFluxType::ELECTRIC || data_type == SurfaceFluxType::MAGNETIC)) + auto Phi_unitful = dimensionlize_flux(flux_data.Phi, flux_data.type); + surface_F.table[format("F_{}_re", flux_data.idx)] << Phi_unitful.real(); + if (has_imaginary && (flux_data.type == SurfaceFluxType::ELECTRIC || + flux_data.type == SurfaceFluxType::MAGNETIC)) { - surface_F.table[format("F_{}_im", idx)] << Phi.imag(); + surface_F.table[format("F_{}_im", flux_data.idx)] << Phi_unitful.imag(); } } surface_F.WriteFullTableTrunc(); @@ -540,51 +487,41 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimens void BaseSolver::SurfacesPostPrinter::AddMeasurementEps(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata) { - if (!do_measurement_eps_) + if (!do_measurement_eps_ || !root_) { return; } using VT = IoData::ValueType; using fmt::format; - // Gather - std::vector eps_data; - eps_data.reserve(post_op.GetSurfacePostOp().eps_surfs.size()); - for (const auto &[idx, data] : post_op.GetSurfacePostOp().eps_surfs) - { - double p = post_op.GetInterfaceParticipation(idx, E_elec); - double tandelta = post_op.GetSurfacePostOp().GetInterfaceLossTangent(idx); - double Q = (p == 0.0 || tandelta == 0.0) ? mfem::infinity() : 1.0 / (tandelta * p); - eps_data.emplace_back(EpsData{idx, p, Q}); - } - - if (!root_) - { - return; - } + // Interface Participation adds energy contriutions E_elec + E_cap + // E_cap returns zero if the solver does not supprot lumped ports. + double E_elec = post_op.GetEFieldEnergy() + post_op.GetLumpedCapacitorEnergy(); + auto eps_data_vec = post_op.GetInterfaceEFieldEnergyAll(); surface_Q.table["idx"] << idx_value_dimensionful; - for (const auto &[idx, p, Q] : eps_data) + for (const auto &eps_data : eps_data_vec) { - surface_Q.table[format("p_{}", idx)] << p; - surface_Q.table[format("Q_{}", idx)] << Q; + double p = post_op.GetInterfaceParticipation(eps_data.idx, E_elec); + double tandelta = eps_data.tandelta; + double Q = (p == 0.0 || tandelta == 0.0) ? mfem::infinity() : 1.0 / (tandelta * p); + surface_Q.table[format("p_{}", eps_data.idx)] << p; + surface_Q.table[format("Q_{}", eps_data.idx)] << Q; } surface_Q.WriteFullTableTrunc(); } void BaseSolver::SurfacesPostPrinter::AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata) { // If surfaces have been specified for postprocessing, compute the corresponding values // and write out to disk. The passed in E_elec is the sum of the E-field and lumped // capacitor energies, and E_mag is the same for the B-field and lumped inductors. - AddMeasurementFlux(idx_value_dimensionful, post_op, E_elec, E_mag, iodata); - AddMeasurementEps(idx_value_dimensionful, post_op, E_elec, E_mag, iodata); + AddMeasurementFlux(idx_value_dimensionful, post_op, iodata); + AddMeasurementEps(idx_value_dimensionful, post_op, iodata); } BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, @@ -690,7 +627,7 @@ void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_E_) + if (!do_measurement_E_ || !root_) { return; } @@ -703,11 +640,6 @@ void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), probe_field.size())) - if (!root_) - { - return; - } - probe_E.table["idx"] << idx_value_dimensionful; size_t i = 0; for (const auto &idx : post_op.GetProbes()) @@ -730,7 +662,7 @@ void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_B_) + if (!do_measurement_B_ || !root_) { return; } @@ -743,11 +675,6 @@ void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), probe_field.size())) - if (!root_) - { - return; - } - probe_B.table["idx"] << idx_value_dimensionful; size_t i = 0; for (const auto &idx : post_op.GetProbes()) @@ -785,7 +712,6 @@ BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(bool do_measure { return; } - error_indicator = TableWithCSVFile(post_dir / "error-indicators.csv"); error_indicator.table.reserve(1, 4); @@ -796,27 +722,16 @@ BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(bool do_measure } void BaseSolver::ErrorIndicatorPostPrinter::PrintIndicatorStatistics( - const PostOperator &post_op, const ErrorIndicator &indicator) + const PostOperator &post_op, const ErrorIndicator::SummaryStatistics &indicator_stats) { - if (!do_measurement_) - { - return; - } - - // MPI Gather - MPI_Comm comm = post_op.GetComm(); - std::array data = {indicator.Norml2(comm), indicator.Min(comm), - indicator.Max(comm), indicator.Mean(comm)}; - - if (!root_) + if (!do_measurement_ || !root_) { return; } - - error_indicator.table["norm"] << data[0]; - error_indicator.table["min"] << data[1]; - error_indicator.table["max"] << data[2]; - error_indicator.table["mean"] << data[3]; + error_indicator.table["norm"] << indicator_stats.norm; + error_indicator.table["min"] << indicator_stats.min; + error_indicator.table["max"] << indicator_stats.max; + error_indicator.table["mean"] << indicator_stats.mean; error_indicator.WriteFullTableTrunc(); } diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index f425789bb..d951cebbc 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -8,6 +8,7 @@ #include #include #include +#include "fem/errorindicator.hpp" #include "utils/filesystem.hpp" #include "utils/tablecsv.hpp" @@ -15,7 +16,6 @@ namespace palace { class DomainPostOperator; -class ErrorIndicator; class FiniteElementSpaceHierarchy; class IoData; class Mesh; @@ -45,10 +45,9 @@ class BaseSolver public: DomainsPostPrinter() = default; DomainsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const DomainPostOperator &dom_post_op, - const std::string &idx_col_name, int n_expected_rows); + const PostOperator &post_op, const std::string &idx_col_name, + int n_expected_rows); void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, double E_cap, double E_ind, const IoData &iodata); }; @@ -67,11 +66,11 @@ class BaseSolver const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata); + const IoData &iodata); void AddMeasurementFlux(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata); + const IoData &iodata); void AddMeasurementEps(double idx_value_dimensionful, const PostOperator &post_op, - double E_elec, double E_mag, const IoData &iodata); + const IoData &iodata); }; // Common probe postprocessing for all simulation types. @@ -114,7 +113,7 @@ class BaseSolver ErrorIndicatorPostPrinter(bool do_measurement, bool root, const fs::path &post_dir); void PrintIndicatorStatistics(const PostOperator &post_op, - const ErrorIndicator &indicator); + const ErrorIndicator::SummaryStatistics &indicator_stats); }; // Performs a solve using the mesh sequence, then reports error indicators and the number diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index 5d80de0f4..2ce50e37c 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -187,22 +187,25 @@ ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator B *= -1.0 / (1i * omega); post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); - post_op.UpdatePorts(space_op.GetLumpedPortOp(), space_op.GetWavePortOp(), omega); - const double E_elec = post_op.GetEFieldEnergy(); - const double E_mag = post_op.GetHFieldEnergy(); + post_op.SetFrequency(omega); + post_op.MeasureAll(); + Mpi::Print(" Sol. ||E|| = {:.6e} (||RHS|| = {:.6e})\n", linalg::Norml2(space_op.GetComm(), E), linalg::Norml2(space_op.GetComm(), RHS)); - { - const double J = iodata.DimensionalizeValue(IoData::ValueType::ENERGY, 1.0); - Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, - E_mag * J, (E_elec + E_mag) * J); - } + + const double E_elec = post_op.GetEFieldEnergy(); + const double E_mag = post_op.GetHFieldEnergy(); + + const double J = iodata.DimensionalizeValue(IoData::ValueType::ENERGY, 1.0); + Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, + E_mag * J, (E_elec + E_mag) * J); + // Calculate and record the error indicators. Mpi::Print(" Updating solution error estimates\n"); estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); - post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); + post_results.PostprocessStep(iodata, post_op, space_op, step); } // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); @@ -363,16 +366,18 @@ ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator B *= -1.0 / (1i * omega); post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); - post_op.UpdatePorts(space_op.GetLumpedPortOp(), space_op.GetWavePortOp(), omega); + post_op.SetFrequency(omega); + post_op.MeasureAll(); + + Mpi::Print(" Sol. ||E|| = {:.6e}\n", linalg::Norml2(space_op.GetComm(), E)); + const double E_elec = post_op.GetEFieldEnergy(); const double E_mag = post_op.GetHFieldEnergy(); - Mpi::Print(" Sol. ||E|| = {:.6e}\n", linalg::Norml2(space_op.GetComm(), E)); - { - const double J = iodata.DimensionalizeValue(IoData::ValueType::ENERGY, 1.0); - Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, - E_mag * J, (E_elec + E_mag) * J); - } - post_results.PostprocessStep(iodata, post_op, space_op, step, omega, E_elec, E_mag); + const double J = iodata.DimensionalizeValue(IoData::ValueType::ENERGY, 1.0); + Mpi::Print(" Field energy E ({:.3e} J) + H ({:.3e} J) = {:.3e} J\n", E_elec * J, + E_mag * J, (E_elec + E_mag) * J); + + post_results.PostprocessStep(iodata, post_op, space_op, step); } // Final postprocessing & printing BlockTimer bt0(Timer::POSTPRO); @@ -423,7 +428,7 @@ DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( } void DrivenSolver::CurrentsPostPrinter::AddMeasurement( - double omega, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) + double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) { if (!do_measurement_ || !root_) { @@ -432,7 +437,7 @@ void DrivenSolver::CurrentsPostPrinter::AddMeasurement( using VT = IoData::ValueType; using fmt::format; - surface_I.table["idx"] << iodata.DimensionalizeValue(VT::FREQUENCY, omega); + surface_I.table["idx"] << freq; for (const auto &[idx, data] : surf_j_op) { auto I_inc = data.GetExcitationCurrent(); @@ -482,7 +487,7 @@ DrivenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, } void DrivenSolver::PortsPostPrinter::AddMeasurement( - double omega, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, + double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { if (!do_measurement_ || !root_) @@ -493,7 +498,6 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( // Postprocess the frequency domain lumped port voltages and currents (complex magnitude // = sqrt(2) * RMS). - auto freq = iodata.DimensionalizeValue(VT::FREQUENCY, omega); port_V.table["idx"] << freq; port_I.table["idx"] << freq; @@ -511,8 +515,8 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( port_I.table[fmt::format("inc{}", idx)] << I_inc * unit_A; } - std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); - std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + std::complex V_i = post_op.GetPortVoltage(idx); + std::complex I_i = post_op.GetPortCurrent(idx); port_V.table[fmt::format("re{}", idx)] << V_i.real() * unit_V; port_V.table[fmt::format("im{}", idx)] << V_i.imag() * unit_V; @@ -541,7 +545,6 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( { return; } - // Get excitation index as is currently done: if -1 then no excitation // Already ensured that one of lumped or wave ports are empty for (const auto &[idx, data] : lumped_port_op) @@ -565,7 +568,6 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( { return; } - using fmt::format; port_S = TableWithCSVFile(post_dir / "port-S.csv"); port_S.table.reserve(n_expected_rows, lumped_port_op.Size()); @@ -590,7 +592,7 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( } void DrivenSolver::SParametersPostPrinter::AddMeasurement( - double omega, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, + double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata) { if (!do_measurement_ || !root_) @@ -601,7 +603,7 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( using fmt::format; // Add frequencies - port_S.table["idx"] << iodata.DimensionalizeValue(VT::FREQUENCY, omega); + port_S.table["idx"] << freq; std::vector all_port_indices; for (const auto &[idx, data] : lumped_port_op) @@ -615,15 +617,8 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( for (const auto o_idx : all_port_indices) { - std::complex S_ij; - if (src_lumped_port) - { - S_ij = post_op.GetSParameter(lumped_port_op, o_idx, source_idx); - } - else - { - S_ij = post_op.GetSParameter(wave_port_op, o_idx, source_idx); - } + std::complex S_ij = post_op.GetSParameter(src_lumped_port, o_idx, source_idx); + auto abs_S_ij = 20.0 * std::log10(std::abs(S_ij)); auto arg_S_ij = std::arg(S_ij) * 180.8 / M_PI; @@ -642,7 +637,7 @@ DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( bool root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post_) : delta_post{delta_post_}, write_paraview_fields{delta_post_ > 0}, - domains{true, root, post_dir, post_op.GetDomainPostOp(), "f (GHz)", n_expected_rows}, + domains{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, surfaces{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, probes{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, @@ -660,19 +655,17 @@ DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( void DrivenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, - int step, double omega, - double E_elec, double E_mag) + int step) { + double omega = post_op.GetFrequency().real(); auto freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); - auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); - auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); - domains.AddMeasurement(freq, post_op, E_elec, E_mag, E_cap, E_ind, iodata); - surfaces.AddMeasurement(freq, post_op, E_elec + E_cap, E_mag + E_ind, iodata); - currents.AddMeasurement(omega, space_op.GetSurfaceCurrentOp(), iodata); + domains.AddMeasurement(freq, post_op, iodata); + surfaces.AddMeasurement(freq, post_op, iodata); + currents.AddMeasurement(freq, space_op.GetSurfaceCurrentOp(), iodata); probes.AddMeasurement(freq, post_op, iodata); - ports.AddMeasurement(omega, post_op, space_op.GetLumpedPortOp(), iodata); - s_parameters.AddMeasurement(omega, post_op, space_op.GetLumpedPortOp(), + ports.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), iodata); + s_parameters.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), space_op.GetWavePortOp(), iodata); // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && (step % delta_post == 0)) @@ -687,7 +680,8 @@ void DrivenSolver::PostprocessPrintResults::PostprocessFinal( const PostOperator &post_op, const ErrorIndicator &indicator) { BlockTimer bt0(Timer::POSTPRO); - error_indicator.PrintIndicatorStatistics(post_op, indicator); + auto indicator_stats = indicator.GetSummaryStatistics(post_op.GetComm()); + error_indicator.PrintIndicatorStatistics(post_op, indicator_stats); if (write_paraview_fields) { post_op.WriteFieldsFinal(&indicator); diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index cc1eb8208..1c8bd51f8 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -40,7 +40,7 @@ class DrivenSolver : public BaseSolver CurrentsPostPrinter() = default; CurrentsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); - void AddMeasurement(double omega, const SurfaceCurrentOperator &surf_j_op, + void AddMeasurement(double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata); }; @@ -55,7 +55,7 @@ class DrivenSolver : public BaseSolver PortsPostPrinter() = default; PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, int n_expected_rows); - void AddMeasurement(double omega, const PostOperator &post_op, + void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; @@ -79,7 +79,7 @@ class DrivenSolver : public BaseSolver SParametersPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, int n_expected_rows); - void AddMeasurement(double omega, const PostOperator &post_op, + void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata); }; @@ -102,8 +102,7 @@ class DrivenSolver : public BaseSolver const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, - const SpaceOperator &space_op, int step, double omega, - double E_elec, double E_mag); + const SpaceOperator &space_op, int step); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); }; diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index ca881e60a..7f1611b17 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -315,7 +315,9 @@ EigenSolver::Solve(const std::vector> &mesh) const B *= -1.0 / (1i * omega); post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); - post_op.UpdatePorts(space_op.GetLumpedPortOp(), omega.real()); + post_op.SetFrequency(omega); + post_op.MeasureAll(); + const double E_elec = post_op.GetEFieldEnergy(); const double E_mag = post_op.GetHFieldEnergy(); @@ -326,8 +328,7 @@ EigenSolver::Solve(const std::vector> &mesh) const } // Postprocess state and write fields to file - post_results.PostprocessStep(iodata, post_op, space_op, i, omega, E_elec, E_mag, - error_abs, error_bkwd); + post_results.PostprocessStep(iodata, post_op, space_op, i, error_abs, error_bkwd); // Final write: Different condition than end of loop (i = num_conv - 1) if (i == iodata.solver.eigenmode.n - 1) @@ -338,24 +339,6 @@ EigenSolver::Solve(const std::vector> &mesh) const return {indicator, space_op.GlobalTrueVSize()}; } -namespace -{ - -struct EprLData -{ - int idx; // Lumped port index - double pj; // Inductor energy-participation ratio -}; - -struct EprIOData -{ - int idx; // Lumped port index - double Ql; // Quality factor - double Kl; // κ for loss rate -}; - -} // namespace - void EigenSolver::EigenPostPrinter::PrintStdoutHeader() { if (!root_) @@ -419,9 +402,9 @@ void EigenSolver::EigenPostPrinter::PrintStdoutRow(size_t j) } EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, int n_eig) + const fs::path &post_dir, int n_post) : root_{root}, do_measurement_(do_measurement), - stdout_int_print_width(1 + static_cast(std::log10(n_eig))) + stdout_int_print_width(1 + static_cast(std::log10(n_post))) { // Note: we switch to n_eig rather than n_conv for padding since we don't know n_conv // until solve @@ -430,7 +413,7 @@ EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, return; } eig = TableWithCSVFile(post_dir / "eig.csv"); - eig.table.reserve(n_eig, 6); + eig.table.reserve(n_post, 6); eig.table.insert_column(Column("idx", "m", 0, {}, {}, "")); eig.table.insert_column("f_re", "Re{f} (GHz)"); eig.table.insert_column("f_im", "Im{f} (GHz)"); @@ -441,7 +424,7 @@ EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, } void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, - std::complex omega, + const PostOperator &post_op, double error_bkwd, double error_abs, const IoData &iodata) { @@ -452,7 +435,8 @@ void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, using VT = IoData::ValueType; using fmt::format; - std::complex f = iodata.DimensionalizeValue(VT::FREQUENCY, omega); + std::complex f = + iodata.DimensionalizeValue(VT::FREQUENCY, post_op.GetFrequency()); double Q = (f.imag() == 0.0) ? mfem::infinity() : 0.5 * std::abs(f) / std::abs(f.imag()); eig.table["idx"] << eigen_print_idx; @@ -521,8 +505,8 @@ void EigenSolver::PortsPostPrinter::AddMeasurement(int eigen_print_idx, for (const auto &[idx, data] : lumped_port_op) { - std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); - std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + std::complex V_i = post_op.GetPortVoltage(idx); + std::complex I_i = post_op.GetPortCurrent(idx); port_V.table[fmt::format("re{}", idx)] << V_i.real() * unit_V; port_V.table[fmt::format("im{}", idx)] << V_i.imag() * unit_V; @@ -592,33 +576,22 @@ EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, void EigenSolver::EPRPostPrinter::AddMeasurementEPR( double eigen_print_idx, const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, double E_m, const IoData &iodata) + const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_EPR_) - { - return; - } - - // TODO: Does this include a reduce? - // Write the mode EPR for lumped inductor elements. - std::vector epr_L_data; - epr_L_data.reserve(ports_with_L.size()); - for (const auto idx : ports_with_L) - { - const double pj = post_op.GetInductorParticipation(lumped_port_op, idx, E_m); - epr_L_data.push_back({idx, pj}); - } - - if (!root_) + if (!do_measurement_EPR_ || !root_) { return; } using fmt::format; + double E_elec = post_op.GetEFieldEnergy(); + double E_cap = post_op.GetLumpedCapacitorEnergy(); + double E_m = E_elec + E_cap; + port_EPR.table["idx"] << eigen_print_idx; - for (const auto &[idx, pj] : epr_L_data) + for (const auto idx : ports_with_L) { - port_EPR.table[format("p_{}", idx)] << pj; + port_EPR.table[format("p_{}", idx)] << post_op.GetInductorParticipation(idx, E_m); } port_EPR.AppendRow(); } @@ -626,37 +599,28 @@ void EigenSolver::EPRPostPrinter::AddMeasurementEPR( void EigenSolver::EPRPostPrinter::AddMeasurementQ(double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - std::complex omega, double E_m, const IoData &iodata) { - if (!do_measurement_Q_) - { - return; - } - - // TODO: Does this include a reduce? - // Write the mode EPR for lumped resistor elements. - std::vector epr_IO_data; - epr_IO_data.reserve(ports_with_R.size()); - for (const auto idx : ports_with_R) - { - const double Kl = post_op.GetExternalKappa(lumped_port_op, idx, E_m); - const double Ql = (Kl == 0.0) ? mfem::infinity() : omega.real() / std::abs(Kl); - epr_IO_data.push_back( - {idx, Ql, iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, Kl)}); - } - - if (!root_) + if (!do_measurement_Q_ || !root_) { return; } using fmt::format; + using VT = IoData::ValueType; + + auto omega = post_op.GetFrequency(); + double E_elec = post_op.GetEFieldEnergy(); + double E_cap = post_op.GetLumpedCapacitorEnergy(); + double E_m = E_elec + E_cap; port_EPR.table["idx"] << eigen_print_idx; - for (const auto &[idx, Ql, Kl] : epr_IO_data) + for (const auto idx : ports_with_R) { + double Kl = post_op.GetExternalKappa(idx, E_m); + double Ql = (Kl == 0.0) ? mfem::infinity() : omega.real() / std::abs(Kl); + port_Q.table[format("Ql_{}", idx)] << Ql; - port_Q.table[format("Kl_{}", idx)] << Kl; + port_Q.table[format("Kl_{}", idx)] << iodata.DimensionalizeValue(VT::FREQUENCY, Kl); } port_Q.AppendRow(); } @@ -664,11 +628,10 @@ void EigenSolver::EPRPostPrinter::AddMeasurementQ(double eigen_print_idx, void EigenSolver::EPRPostPrinter::AddMeasurement(double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, - std::complex omega, double E_m, const IoData &iodata) { - AddMeasurementEPR(eigen_print_idx, post_op, lumped_port_op, E_m, iodata); - AddMeasurementQ(eigen_print_idx, post_op, lumped_port_op, omega, E_m, iodata); + AddMeasurementEPR(eigen_print_idx, post_op, lumped_port_op, iodata); + AddMeasurementQ(eigen_print_idx, post_op, lumped_port_op, iodata); } EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, @@ -677,7 +640,7 @@ EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, const SpaceOperator &space_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op.GetDomainPostOp(), "m", n_post}, + domains{true, root, post_dir, post_op, "m", n_post}, surfaces{true, root, post_dir, post_op, "m", n_post}, probes{true, root, post_dir, post_op, "m", n_post}, eigen{true, root, post_dir, n_post}, epr{true, root, post_dir, space_op.GetLumpedPortOp(), n_post}, @@ -685,22 +648,19 @@ EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, { } -void EigenSolver::PostprocessPrintResults::PostprocessStep( - const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, - int step, std::complex omega, double E_elec, double E_mag, double error_abs, - double error_bkward) +void EigenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata, + const PostOperator &post_op, + const SpaceOperator &space_op, + int step, double error_abs, + double error_bkward) { int eigen_print_idx = step + 1; - auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); - auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); - - domains.AddMeasurement(eigen_print_idx, post_op, E_elec, E_mag, E_cap, E_ind, iodata); - surfaces.AddMeasurement(eigen_print_idx, post_op, E_elec + E_cap, E_mag + E_ind, iodata); + domains.AddMeasurement(eigen_print_idx, post_op, iodata); + surfaces.AddMeasurement(eigen_print_idx, post_op, iodata); probes.AddMeasurement(eigen_print_idx, post_op, iodata); - eigen.AddMeasurement(eigen_print_idx, omega, error_bkward, error_abs, iodata); - epr.AddMeasurement(eigen_print_idx, post_op, space_op.GetLumpedPortOp(), omega, - E_elec + E_cap, iodata); + eigen.AddMeasurement(eigen_print_idx, post_op, error_bkward, error_abs, iodata); + epr.AddMeasurement(eigen_print_idx, post_op, space_op.GetLumpedPortOp(), iodata); // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && step < n_post) { @@ -714,7 +674,8 @@ void EigenSolver::PostprocessPrintResults::PostprocessFinal(const PostOperator & const ErrorIndicator &indicator) { BlockTimer bt0(Timer::POSTPRO); - error_indicator.PrintIndicatorStatistics(post_op, indicator); + auto indicator_stats = indicator.GetSummaryStatistics(post_op.GetComm()); + error_indicator.PrintIndicatorStatistics(post_op, indicator_stats); if (write_paraview_fields) { post_op.WriteFieldsFinal(&indicator); diff --git a/palace/drivers/eigensolver.hpp b/palace/drivers/eigensolver.hpp index 4fe4623c6..66ab809bf 100644 --- a/palace/drivers/eigensolver.hpp +++ b/palace/drivers/eigensolver.hpp @@ -39,7 +39,7 @@ class EigenSolver : public BaseSolver EigenPostPrinter() = default; EigenPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, int n_post); - void AddMeasurement(int eigen_print_idx, std::complex omega, double error_bkwd, + void AddMeasurement(int eigen_print_idx, const PostOperator &post_op, double error_bkwd, double error_abs, const IoData &iodata); }; @@ -76,15 +76,12 @@ class EigenSolver : public BaseSolver const LumpedPortOperator &lumped_port_op, int n_expected_rows); void AddMeasurementEPR(double eigen_print_idx, const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, double E_m, - const IoData &iodata); + const LumpedPortOperator &lumped_port_op, const IoData &iodata); void AddMeasurementQ(double eigen_print_idx, const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - std::complex omega, double E_m, const IoData &iodata); + const LumpedPortOperator &lumped_port_op, const IoData &iodata); void AddMeasurement(double eigen_print_idx, const PostOperator &post_op, - const LumpedPortOperator &lumped_port_op, - std::complex omega, double E_m, const IoData &iodata); + const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; struct PostprocessPrintResults @@ -104,9 +101,8 @@ class EigenSolver : public BaseSolver const PostOperator &post_op, const SpaceOperator &space_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, - const SpaceOperator &space_op, int step, - std::complex omega, double E_elec, double E_mag, - double error_abs, double error_bkward); + const SpaceOperator &space_op, int step, double error_abs, + double error_bkward); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); }; diff --git a/palace/drivers/electrostaticsolver.cpp b/palace/drivers/electrostaticsolver.cpp index 412e97287..5efa5af9a 100644 --- a/palace/drivers/electrostaticsolver.cpp +++ b/palace/drivers/electrostaticsolver.cpp @@ -77,6 +77,7 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const Grad.AddMult(V[step], E, -1.0); post_op.SetVGridFunction(V[step]); post_op.SetEGridFunction(E); + post_op.MeasureAll(); const double E_elec = post_op.GetEFieldEnergy(); Mpi::Print(" Sol. ||V|| = {:.6e} (||RHS|| = {:.6e})\n", linalg::Norml2(laplace_op.GetComm(), V[step]), @@ -91,7 +92,7 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const estimator.AddErrorIndicator(E, E_elec, indicator); // Postprocess field solutions and optionally write solution to disk. - post_results.PostprocessStep(iodata, post_op, step, idx, E_elec); + post_results.PostprocessStep(iodata, post_op, step, idx); // Next terminal. step++; @@ -203,7 +204,7 @@ void ElectrostaticSolver::PostprocessTerminals( ElectrostaticSolver::PostprocessPrintResults::PostprocessPrintResults( bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, + domains{true, root, post_dir, post_op, "i", n_post}, surfaces{true, root, post_dir, post_op, "i", n_post}, probes{true, root, post_dir, post_op, "i", n_post}, error_indicator{true, root, post_dir} @@ -211,10 +212,10 @@ ElectrostaticSolver::PostprocessPrintResults::PostprocessPrintResults( } void ElectrostaticSolver::PostprocessPrintResults::PostprocessStep( - const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_elec) + const IoData &iodata, const PostOperator &post_op, int step, int idx) { - domains.AddMeasurement(idx, post_op, E_elec, 0.0, 0.0, 0.0, iodata); - surfaces.AddMeasurement(idx, post_op, E_elec, 0.0, iodata); + domains.AddMeasurement(idx, post_op, iodata); + surfaces.AddMeasurement(idx, post_op, iodata); probes.AddMeasurement(idx, post_op, iodata); // The internal GridFunctions in PostOperator have already been set from V: if (write_paraview_fields && step < n_post) @@ -229,7 +230,8 @@ void ElectrostaticSolver::PostprocessPrintResults::PostprocessFinal( const PostOperator &post_op, const ErrorIndicator &indicator) { BlockTimer bt0(Timer::POSTPRO); - error_indicator.PrintIndicatorStatistics(post_op, indicator); + auto indicator_stats = indicator.GetSummaryStatistics(post_op.GetComm()); + error_indicator.PrintIndicatorStatistics(post_op, indicator_stats); if (write_paraview_fields) { post_op.WriteFieldsFinal(&indicator); diff --git a/palace/drivers/electrostaticsolver.hpp b/palace/drivers/electrostaticsolver.hpp index 0a71e8fbd..283d6c540 100644 --- a/palace/drivers/electrostaticsolver.hpp +++ b/palace/drivers/electrostaticsolver.hpp @@ -45,7 +45,7 @@ class ElectrostaticSolver : public BaseSolver PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, - int idx, double E_elec); + int idx); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); }; diff --git a/palace/drivers/magnetostaticsolver.cpp b/palace/drivers/magnetostaticsolver.cpp index d5fac30f9..f4321daf2 100644 --- a/palace/drivers/magnetostaticsolver.cpp +++ b/palace/drivers/magnetostaticsolver.cpp @@ -79,6 +79,7 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const Curl.Mult(A[step], B); post_op.SetAGridFunction(A[step]); post_op.SetBGridFunction(B); + post_op.MeasureAll(); const double E_mag = post_op.GetHFieldEnergy(); Mpi::Print(" Sol. ||A|| = {:.6e} (||RHS|| = {:.6e})\n", linalg::Norml2(curlcurl_op.GetComm(), A[step]), @@ -94,7 +95,7 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const estimator.AddErrorIndicator(B, E_mag, indicator); // Postprocess field solutions and optionally write solution to disk. - post_results.PostprocessStep(iodata, post_op, step, idx, E_mag); + post_results.PostprocessStep(iodata, post_op, step, idx); // Next source. step++; @@ -208,7 +209,7 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, MagnetostaticSolver::PostprocessPrintResults::PostprocessPrintResults( bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op.GetDomainPostOp(), "i", n_post}, + domains{true, root, post_dir, post_op, "i", n_post}, surfaces{true, root, post_dir, post_op, "i", n_post}, probes{true, root, post_dir, post_op, "i", n_post}, error_indicator{true, root, post_dir} @@ -216,10 +217,10 @@ MagnetostaticSolver::PostprocessPrintResults::PostprocessPrintResults( } void MagnetostaticSolver::PostprocessPrintResults::PostprocessStep( - const IoData &iodata, const PostOperator &post_op, int step, int idx, double E_mag) + const IoData &iodata, const PostOperator &post_op, int step, int idx) { - domains.AddMeasurement(idx, post_op, 0.0, E_mag, 0.0, 0.0, iodata); - surfaces.AddMeasurement(idx, post_op, 0.0, E_mag, iodata); + domains.AddMeasurement(idx, post_op, iodata); + surfaces.AddMeasurement(idx, post_op, iodata); probes.AddMeasurement(idx, post_op, iodata); // The internal GridFunctions in PostOperator have already been set from A: if (write_paraview_fields && step < n_post) @@ -234,7 +235,8 @@ void MagnetostaticSolver::PostprocessPrintResults::PostprocessFinal( const PostOperator &post_op, const ErrorIndicator &indicator) { BlockTimer bt0(Timer::POSTPRO); - error_indicator.PrintIndicatorStatistics(post_op, indicator); + auto indicator_stats = indicator.GetSummaryStatistics(post_op.GetComm()); + error_indicator.PrintIndicatorStatistics(post_op, indicator_stats); if (write_paraview_fields) { post_op.WriteFieldsFinal(&indicator); diff --git a/palace/drivers/magnetostaticsolver.hpp b/palace/drivers/magnetostaticsolver.hpp index a95cf91ef..608526c24 100644 --- a/palace/drivers/magnetostaticsolver.hpp +++ b/palace/drivers/magnetostaticsolver.hpp @@ -37,7 +37,7 @@ class MagnetostaticSolver : public BaseSolver PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, const PostOperator &post_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, - int idx, double E_mag); + int idx); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); }; diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index 20ddb8b45..aafbba48b 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -109,7 +109,8 @@ TransientSolver::Solve(const std::vector> &mesh) const const Vector &B = time_op.GetB(); post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); - post_op.UpdatePorts(space_op.GetLumpedPortOp()); + post_op.MeasureAll(); + const double E_elec = post_op.GetEFieldEnergy(); const double E_mag = post_op.GetHFieldEnergy(); Mpi::Print(" Sol. ||E|| = {:.6e}, ||B|| = {:.6e}\n", @@ -124,8 +125,7 @@ TransientSolver::Solve(const std::vector> &mesh) const Mpi::Print(" Updating solution error estimates\n"); estimator.AddErrorIndicator(E, B, E_elec + E_mag, indicator); - post_results.PostprocessStep(iodata, post_op, space_op, step, t, J_coef(t), E_elec, - E_mag); + post_results.PostprocessStep(iodata, post_op, space_op, step, t, J_coef(t)); } // Final postprocessing & printing BlockTimer bt1(Timer::POSTPRO); @@ -358,8 +358,8 @@ void TransientSolver::PortsPostPrinter::AddMeasurement( port_I.table[format("inc{}", idx)] << I_inc * unit_A; } - std::complex V_i = post_op.GetPortVoltage(lumped_port_op, idx); - std::complex I_i = post_op.GetPortCurrent(lumped_port_op, idx); + std::complex V_i = post_op.GetPortVoltage(idx); + std::complex I_i = post_op.GetPortCurrent(idx); port_V.table[format("re{}", idx)] << V_i.real() * unit_V; port_I.table[format("re{}", idx)] << I_i.real() * unit_A; @@ -372,7 +372,7 @@ TransientSolver::PostprocessPrintResults::PostprocessPrintResults( bool root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post_) : delta_post{delta_post_}, write_paraview_fields(delta_post_ > 0), - domains{true, root, post_dir, post_op.GetDomainPostOp(), "t (ns)", n_expected_rows}, + domains{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, surfaces{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, probes{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, @@ -383,14 +383,12 @@ TransientSolver::PostprocessPrintResults::PostprocessPrintResults( void TransientSolver::PostprocessPrintResults::PostprocessStep( const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, - int step, double t, double J_coef, double E_elec, double E_mag) + int step, double t, double J_coef) { auto time = iodata.DimensionalizeValue(IoData::ValueType::TIME, t); - auto E_cap = post_op.GetLumpedCapacitorEnergy(space_op.GetLumpedPortOp()); - auto E_ind = post_op.GetLumpedInductorEnergy(space_op.GetLumpedPortOp()); - domains.AddMeasurement(time, post_op, E_elec, E_mag, E_cap, E_ind, iodata); - surfaces.AddMeasurement(time, post_op, E_elec + E_cap, E_mag + E_ind, iodata); + domains.AddMeasurement(time, post_op, iodata); + surfaces.AddMeasurement(time, post_op, iodata); currents.AddMeasurement(t, J_coef, space_op.GetSurfaceCurrentOp(), iodata); probes.AddMeasurement(time, post_op, iodata); ports.AddMeasurement(t, J_coef, post_op, space_op.GetLumpedPortOp(), iodata); @@ -407,7 +405,8 @@ void TransientSolver::PostprocessPrintResults::PostprocessFinal( const PostOperator &post_op, const ErrorIndicator &indicator) { BlockTimer bt0(Timer::POSTPRO); - error_indicator.PrintIndicatorStatistics(post_op, indicator); + auto indicator_stats = indicator.GetSummaryStatistics(post_op.GetComm()); + error_indicator.PrintIndicatorStatistics(post_op, indicator_stats); if (write_paraview_fields) { post_op.WriteFieldsFinal(&indicator); diff --git a/palace/drivers/transientsolver.hpp b/palace/drivers/transientsolver.hpp index ab2413c70..82d6edc97 100644 --- a/palace/drivers/transientsolver.hpp +++ b/palace/drivers/transientsolver.hpp @@ -75,8 +75,7 @@ class TransientSolver : public BaseSolver const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, - const SpaceOperator &space_op, int step, double t, double J_coef, - double E_elec, double E_mag); + const SpaceOperator &space_op, int step, double t, double J_coef); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); }; diff --git a/palace/fem/errorindicator.hpp b/palace/fem/errorindicator.hpp index c0f301848..23e27f046 100644 --- a/palace/fem/errorindicator.hpp +++ b/palace/fem/errorindicator.hpp @@ -60,6 +60,19 @@ class ErrorIndicator // Return the mean local error indicator. auto Mean(MPI_Comm comm) const { return linalg::Mean(comm, local); } + + struct SummaryStatistics + { + double norm; + double min; + double max; + double mean; + }; + + SummaryStatistics GetSummaryStatistics(MPI_Comm comm) const + { + return {Norml2(comm), Min(comm), Max(comm), Mean(comm)}; + } }; } // namespace palace diff --git a/palace/fem/interpolator.cpp b/palace/fem/interpolator.cpp index 01e3468f3..8353815ad 100644 --- a/palace/fem/interpolator.cpp +++ b/palace/fem/interpolator.cpp @@ -144,7 +144,7 @@ std::vector> InterpolationOperator::ProbeField(const GridFu } else { - return std::vector>(vr.begin(), vr.end()); + return {vr.begin(), vr.end()}; } } diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index b737ddec4..80b02819a 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -38,17 +38,17 @@ PostOperator::PostOperator(const IoData &iodata, SpaceOperator &space_op, surf_post_op(iodata, space_op.GetMaterialOp(), space_op.GetH1Space()), dom_post_op(iodata, space_op.GetMaterialOp(), space_op.GetNDSpace(), space_op.GetRTSpace()), + interp_op(iodata, space_op.GetNDSpace()), lumped_port_op(&(space_op.GetLumpedPortOp())), + wave_port_op(&(space_op.GetWavePortOp())), E(std::make_unique(space_op.GetNDSpace(), iodata.problem.type != config::ProblemData::Type::TRANSIENT)), B(std::make_unique(space_op.GetRTSpace(), iodata.problem.type != config::ProblemData::Type::TRANSIENT)), - lumped_port_init(false), wave_port_init(false), paraview(CreateParaviewPath(iodata, name), &space_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", - &space_op.GetNDSpace().GetParMesh()), - interp_op(iodata, space_op.GetNDSpace()) + &space_op.GetNDSpace().GetParMesh()) { U_e = std::make_unique>(*E, mat_op); U_m = std::make_unique>(*B, mat_op); @@ -86,8 +86,7 @@ PostOperator::PostOperator(const IoData &iodata, LaplaceOperator &laplace_op, surf_post_op(iodata, laplace_op.GetMaterialOp(), laplace_op.GetH1Space()), dom_post_op(iodata, laplace_op.GetMaterialOp(), laplace_op.GetH1Space()), E(std::make_unique(laplace_op.GetNDSpace())), - V(std::make_unique(laplace_op.GetH1Space())), lumped_port_init(false), - wave_port_init(false), + V(std::make_unique(laplace_op.GetH1Space())), paraview(CreateParaviewPath(iodata, name), &laplace_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", &laplace_op.GetNDSpace().GetParMesh()), @@ -113,8 +112,7 @@ PostOperator::PostOperator(const IoData &iodata, CurlCurlOperator &curlcurl_op, surf_post_op(iodata, curlcurl_op.GetMaterialOp(), curlcurl_op.GetH1Space()), dom_post_op(iodata, curlcurl_op.GetMaterialOp(), curlcurl_op.GetNDSpace()), B(std::make_unique(curlcurl_op.GetRTSpace())), - A(std::make_unique(curlcurl_op.GetNDSpace())), lumped_port_init(false), - wave_port_init(false), + A(std::make_unique(curlcurl_op.GetNDSpace())), paraview(CreateParaviewPath(iodata, name), &curlcurl_op.GetNDSpace().GetParMesh()), paraview_bdr(CreateParaviewPath(iodata, name) + "_boundary", &curlcurl_op.GetNDSpace().GetParMesh()), @@ -274,7 +272,7 @@ void PostOperator::SetEGridFunction(const ComplexVector &e, bool exchange_face_n E->Real().ExchangeFaceNbrData(); // Ready for parallel comm on shared faces E->Imag().ExchangeFaceNbrData(); } - lumped_port_init = wave_port_init = false; + ClearAllMeasurementCache(); } void PostOperator::SetBGridFunction(const ComplexVector &b, bool exchange_face_nbr_data) @@ -289,7 +287,7 @@ void PostOperator::SetBGridFunction(const ComplexVector &b, bool exchange_face_n B->Real().ExchangeFaceNbrData(); // Ready for parallel comm on shared faces B->Imag().ExchangeFaceNbrData(); } - lumped_port_init = wave_port_init = false; + ClearAllMeasurementCache(); } void PostOperator::SetEGridFunction(const Vector &e, bool exchange_face_nbr_data) @@ -302,7 +300,7 @@ void PostOperator::SetEGridFunction(const Vector &e, bool exchange_face_nbr_data { E->Real().ExchangeFaceNbrData(); } - lumped_port_init = wave_port_init = false; + ClearAllMeasurementCache(); } void PostOperator::SetBGridFunction(const Vector &b, bool exchange_face_nbr_data) @@ -315,7 +313,7 @@ void PostOperator::SetBGridFunction(const Vector &b, bool exchange_face_nbr_data { B->Real().ExchangeFaceNbrData(); } - lumped_port_init = wave_port_init = false; + ClearAllMeasurementCache(); } void PostOperator::SetVGridFunction(const Vector &v, bool exchange_face_nbr_data) @@ -328,6 +326,7 @@ void PostOperator::SetVGridFunction(const Vector &v, bool exchange_face_nbr_data { V->Real().ExchangeFaceNbrData(); } + ClearAllMeasurementCache(); } void PostOperator::SetAGridFunction(const Vector &a, bool exchange_face_nbr_data) @@ -340,66 +339,282 @@ void PostOperator::SetAGridFunction(const Vector &a, bool exchange_face_nbr_data { A->Real().ExchangeFaceNbrData(); } + ClearAllMeasurementCache(); } -double PostOperator::GetEFieldEnergy() const +// Measurements + +void PostOperator::ClearAllMeasurementCache() { - if (V) + // Clear Cache: Save omega since this set by hand like fields E,... + bool has_omega = measurment_cache.omega.has_value(); + std::complex omega; + if (has_omega) { - return dom_post_op.GetElectricFieldEnergy(*V); + omega = *measurment_cache.omega; } - else + + measurment_cache = {}; + + if (has_omega) { - MFEM_VERIFY(E, "PostOperator is not configured for electric field energy calculation!"); - return dom_post_op.GetElectricFieldEnergy(*E); + measurment_cache.omega = omega; } } -double PostOperator::GetHFieldEnergy() const +void PostOperator::MeasureAll() { - if (A) + ClearAllMeasurementCache(); + + // Domain Energy: Electric Field Contribution + if (V || E) { - return dom_post_op.GetMagneticFieldEnergy(*A); + GetEFieldEnergy(); // if (dom_post_op.M_elec) + if (dom_post_op.M_i.size() > 0) + { + GetEFieldEnergy(dom_post_op.M_i.begin()->first); // Measures all domains + } } - else + // Domain Energy: Magnetic Field Contribution + if (A || B) + { + GetHFieldEnergy(); // if (dom_post_op.M_mag) + if (dom_post_op.M_i.size() > 0) + { + GetHFieldEnergy(dom_post_op.M_i.begin()->first); // Measures all domains + } + } + + if (E || B) + { + GetSurfaceFluxAll(); + } + if (E) + { + GetInterfaceEFieldEnergyAll(); + ProbeEField(); + } + if (B) + { + ProbeBField(); + } + if (E && B) + { + if (lumped_port_op != nullptr) + { + MeasureLumpedPorts(); + } + if (wave_port_op != nullptr) + { + MeasureWavePorts(); + } + } +} + +void PostOperator::SetFrequency(double omega) +{ + measurment_cache.omega = std::complex(omega); +} + +void PostOperator::SetFrequency(std::complex omega) +{ + measurment_cache.omega = omega; +} + +std::complex PostOperator::GetFrequency() const +{ + MFEM_VERIFY(measurment_cache.omega.has_value(), + "Frequency value omega has not been correctly set!"); + return *measurment_cache.omega; +} + +double PostOperator::GetEFieldEnergy() const +{ + if (!measurment_cache.domain_E_field_energy_all.has_value()) + { + if (V) + { + measurment_cache.domain_E_field_energy_all = dom_post_op.GetElectricFieldEnergy(*V); + } + else if (E) + { + measurment_cache.domain_E_field_energy_all = dom_post_op.GetElectricFieldEnergy(*E); + } + else + { + // No failure: returns zero + measurment_cache.domain_E_field_energy_all = 0.0; + } + } + return *measurment_cache.domain_E_field_energy_all; +} + +double PostOperator::GetHFieldEnergy() const +{ + if (!measurment_cache.domain_H_field_energy_all.has_value()) { - MFEM_VERIFY(B, "PostOperator is not configured for magnetic field energy calculation!"); - return dom_post_op.GetMagneticFieldEnergy(*B); + if (A) + { + measurment_cache.domain_H_field_energy_all = dom_post_op.GetMagneticFieldEnergy(*A); + } + else if (B) + { + measurment_cache.domain_H_field_energy_all = dom_post_op.GetMagneticFieldEnergy(*B); + } + else + { + // No failure: returns zero + measurment_cache.domain_H_field_energy_all = 0.0; + } } + return *measurment_cache.domain_H_field_energy_all; } double PostOperator::GetEFieldEnergy(int idx) const { - if (V) + if (!measurment_cache.domain_E_field_energy_i.has_value()) { - return dom_post_op.GetDomainElectricFieldEnergy(idx, *V); + // Do all measurements + measurment_cache.domain_E_field_energy_i.emplace(); + for (const auto &[idx, data] : dom_post_op.M_i) + { + double out; + if (V) + { + out = dom_post_op.GetDomainElectricFieldEnergy(idx, *V); + } + else if (E) + { + out = dom_post_op.GetDomainElectricFieldEnergy(idx, *E); + } + else + { + // No failure: returns zero + out = 0.0; + } + measurment_cache.domain_E_field_energy_i->emplace(idx, out); + } } - else + auto it = measurment_cache.domain_E_field_energy_i->find(idx); + if (it == measurment_cache.domain_E_field_energy_i->end()) { - MFEM_VERIFY(E, "PostOperator is not configured for electric field energy calculation!"); - return dom_post_op.GetDomainElectricFieldEnergy(idx, *E); + MFEM_ABORT(fmt::format("Could not find domain index {} for E field energy!", idx)); } + return it->second; } double PostOperator::GetHFieldEnergy(int idx) const { - if (A) + if (!measurment_cache.domain_H_field_energy_i.has_value()) { - return dom_post_op.GetDomainMagneticFieldEnergy(idx, *A); + // Do all measurements + measurment_cache.domain_H_field_energy_i.emplace(); + for (const auto &[idx, data] : dom_post_op.M_i) + { + double out; + if (A) + { + out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *A); + } + else if (B) + { + out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *B); + } + else + { + // No failure: returns zero + out = 0.0; + } + measurment_cache.domain_H_field_energy_i->emplace(idx, out); + } } - else + auto it = measurment_cache.domain_H_field_energy_i->find(idx); + if (it == measurment_cache.domain_H_field_energy_i->end()) { - MFEM_VERIFY(B, "PostOperator is not configured for magnetic field energy calculation!"); - return dom_post_op.GetDomainMagneticFieldEnergy(idx, *B); + MFEM_ABORT(fmt::format("Could not find domain index {} for H field energy!", idx)); } + return it->second; } -std::complex PostOperator::GetSurfaceFlux(int idx) const +// Code Note: for returning the full vector we chose name GetSurfaceFluxAll() rather than +// GetSurfaceFlux(), to keep consistancy with GetEFieldEnergy(). GetEFieldEnergy() returns +// the total energy (calculated differently), not the vector of the individual +// GetHFieldEnergy(int idx). +std::vector PostOperator::GetSurfaceFluxAll() const { // Compute the flux through a surface as Φ_j = ∫ F ⋅ n_j dS, with F = B, F = ε D, or F = // E x H. The special coefficient is used to avoid issues evaluating MFEM GridFunctions // which are discontinuous at interior boundary elements. - return surf_post_op.GetSurfaceFlux(idx, E.get(), B.get()); + if (!measurment_cache.surface_flux_i.has_value()) + { + // Do all measurements + MFEM_VERIFY( + E || B, + "PostOperator needs either electric or magnetic field for flux calculation!"); + measurment_cache.surface_flux_i.emplace(); + measurment_cache.surface_flux_i->reserve(surf_post_op.flux_surfs.size()); + for (const auto &[idx, data] : surf_post_op.flux_surfs) + { + measurment_cache.surface_flux_i->emplace_back( + FluxData{idx, surf_post_op.GetSurfaceFlux(idx, E.get(), B.get()), data.type}); + } + } + return *measurment_cache.surface_flux_i; +} + +PostOperator::FluxData PostOperator::GetSurfaceFlux(int idx) const +{ + if (!measurment_cache.surface_flux_i.has_value()) + { + GetSurfaceFluxAll(); + } + auto it = std::find_if(measurment_cache.surface_flux_i->begin(), + measurment_cache.surface_flux_i->end(), + [idx](const auto &d) { return d.idx == idx; }); + if (it == measurment_cache.surface_flux_i->end()) + { + MFEM_ABORT(fmt::format("Could not find surface index {} for flux!", idx)); + } + return *it; +} + +std::vector PostOperator::GetInterfaceEFieldEnergyAll() const +{ + // Compute the surface dielectric participation ratio and associated quality factor for + // the material interface given by index idx. We have: + // 1/Q_mj = p_mj tan(δ)_j + // with: + // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). + if (!measurment_cache.interface_eps_i.has_value()) + { + // Do all measurements + MFEM_VERIFY(E, "Electric field solution required for E field interface energy!"); + measurment_cache.interface_eps_i.emplace(); + measurment_cache.interface_eps_i->reserve(surf_post_op.eps_surfs.size()); + for (const auto &[idx, data] : surf_post_op.eps_surfs) + { + measurment_cache.interface_eps_i->emplace_back( + InterfaceData{idx, surf_post_op.GetInterfaceElectricFieldEnergy(idx, *E), + surf_post_op.GetInterfaceLossTangent(idx)}); + } + } + return *measurment_cache.interface_eps_i; +} + +PostOperator::InterfaceData PostOperator::GetInterfaceEFieldEnergy(int idx) const +{ + if (!measurment_cache.interface_eps_i.has_value()) + { + GetInterfaceEFieldEnergyAll(); + } + auto it = std::find_if(measurment_cache.interface_eps_i->begin(), + measurment_cache.interface_eps_i->end(), + [idx](const auto &d) { return d.idx == idx; }); + if (it == measurment_cache.interface_eps_i->end()) + { + MFEM_ABORT(fmt::format("Could not find surface index {} for interface energy!", idx)); + } + return *it; } double PostOperator::GetInterfaceParticipation(int idx, double E_m) const @@ -410,25 +625,33 @@ double PostOperator::GetInterfaceParticipation(int idx, double E_m) const // with: // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). MFEM_VERIFY(E, "Surface Q not defined, no electric field solution found!"); - return surf_post_op.GetInterfaceElectricFieldEnergy(idx, *E) / E_m; + auto data = GetInterfaceEFieldEnergy(idx); + return data.energy / E_m; } -void PostOperator::UpdatePorts(const LumpedPortOperator &lumped_port_op, double omega) +void PostOperator::MeasureLumpedPorts() const { - MFEM_VERIFY(E && B, "Incorrect usage of PostOperator::UpdatePorts!"); - if (lumped_port_init) + MFEM_VERIFY(E && B && lumped_port_op != nullptr, + "Incorrect usage of PostOperator::MeasureLumpedPorts!"); + if (measurment_cache.lumped_port_vi.has_value()) { - return; + measurment_cache.lumped_port_vi->clear(); } - for (const auto &[idx, data] : lumped_port_op) + else + { + measurment_cache.lumped_port_vi.emplace(); + } + for (const auto &[idx, data] : *lumped_port_op) { - auto &vi = lumped_port_vi[idx]; + auto &vi = (*measurment_cache.lumped_port_vi)[idx]; vi.P = data.GetPower(*E, *B); vi.V = data.GetVoltage(*E); if (HasImag()) { // Compute current from the port impedance, separate contributions for R, L, C // branches. + // Get value and make real: Matches current behaviour (even for eigensolver!) + auto omega = GetFrequency().real(); MFEM_VERIFY( omega > 0.0, "Frequency domain lumped port postprocessing requires nonzero frequency!"); @@ -453,182 +676,238 @@ void PostOperator::UpdatePorts(const LumpedPortOperator &lumped_port_op, double vi.I[1] = vi.I[2] = vi.S = 0.0; } } - lumped_port_init = true; } -void PostOperator::UpdatePorts(const WavePortOperator &wave_port_op, double omega) +void PostOperator::MeasureWavePorts() const { - MFEM_VERIFY(HasImag() && E && B, "Incorrect usage of PostOperator::UpdatePorts!"); - if (wave_port_init) + + MFEM_VERIFY(E && B && wave_port_op != nullptr, + "Incorrect usage of PostOperator::MeasureWavePorts!"); + if (measurment_cache.wave_port_vi.has_value()) { - return; + measurment_cache.wave_port_vi->clear(); } - for (const auto &[idx, data] : wave_port_op) + else { + measurment_cache.wave_port_vi.emplace(); + } + if (!HasImag()) + { + return; // Wave ports need Imag; leave empty otherwise // TODO: Fix in long run + } + for (const auto &[idx, data] : *wave_port_op) + { + MFEM_VERIFY(measurment_cache.omega.has_value(), + "Measuring port currents with Imag fields, requires frequency to be set " + "with SetFrequency!"); + + // Get value and make real: Matches current behaviour + auto omega = measurment_cache.omega->real(); + MFEM_VERIFY(omega > 0.0, "Frequency domain wave port postprocessing requires nonzero frequency!"); - auto &vi = wave_port_vi[idx]; + auto &vi = (*measurment_cache.wave_port_vi)[idx]; vi.P = data.GetPower(*E, *B); vi.S = data.GetSParameter(*E); vi.V = vi.I[0] = vi.I[1] = vi.I[2] = 0.0; // Not yet implemented // (Z = V² / P, I = V / Z) } - wave_port_init = true; } -double PostOperator::GetLumpedInductorEnergy(const LumpedPortOperator &lumped_port_op) const +void PostOperator::ValidateDoPortMeasurement() const +{ + if (!measurment_cache.lumped_port_vi.has_value()) + { + if (lumped_port_op != nullptr) + { + MeasureLumpedPorts(); + } + else + { + MFEM_ABORT("A lumped port measurement called, but the lumped port operator is not " + "defined by the solver.") + } + } + if (!measurment_cache.wave_port_vi.has_value()) + { + if (wave_port_op != nullptr) + { + MeasureWavePorts(); + } + else + { + MFEM_ABORT("A wave port measurement called, but the wave port operator is not " + "defined by the solver.") + } + } +} + +double PostOperator::GetLumpedInductorEnergy() const { // Add contribution due to all inductive lumped boundaries in the model: // E_ind = ∑_j 1/2 L_j I_mj². - double U = 0.0; - for (const auto &[idx, data] : lumped_port_op) + if (!measurment_cache.lumped_port_inductor_energy.has_value()) { - if (std::abs(data.L) > 0.0) + // No failure if space has no lumped ports: Returns zero + double U = 0.0; + if (lumped_port_op != nullptr) { - std::complex I_j = - GetPortCurrent(lumped_port_op, idx, LumpedPortData::Branch::L); - U += 0.5 * std::abs(data.L) * std::real(I_j * std::conj(I_j)); + for (const auto &[idx, data] : *lumped_port_op) + { + if (std::abs(data.L) > 0.0) + { + std::complex I_j = GetPortCurrent(idx, LumpedPortData::Branch::L); + U += 0.5 * std::abs(data.L) * std::real(I_j * std::conj(I_j)); + } + } } + measurment_cache.lumped_port_inductor_energy = U; } - return U; + return *measurment_cache.lumped_port_inductor_energy; } -double -PostOperator::GetLumpedCapacitorEnergy(const LumpedPortOperator &lumped_port_op) const +double PostOperator::GetLumpedCapacitorEnergy() const { // Add contribution due to all capacitive lumped boundaries in the model: // E_cap = ∑_j 1/2 C_j V_mj². - double U = 0.0; - for (const auto &[idx, data] : lumped_port_op) + if (!measurment_cache.lumped_port_capacitor_energy.has_value()) { - if (std::abs(data.C) > 0.0) + // No failure if space has no lumped ports: Returns zero + double U = 0.0; + if (lumped_port_op != nullptr) { - std::complex V_j = GetPortVoltage(lumped_port_op, idx); - U += 0.5 * std::abs(data.C) * std::real(V_j * std::conj(V_j)); + for (const auto &[idx, data] : *lumped_port_op) + { + if (std::abs(data.C) > 0.0) + { + std::complex V_j = GetPortVoltage(idx); + U += 0.5 * std::abs(data.C) * std::real(V_j * std::conj(V_j)); + } + } } + measurment_cache.lumped_port_capacitor_energy = U; } - return U; + return *measurment_cache.lumped_port_capacitor_energy; } -std::complex PostOperator::GetSParameter(const LumpedPortOperator &lumped_port_op, - int idx, int source_idx) const +std::complex PostOperator::GetSParameter(bool is_lumped_port, int idx, + int source_idx) const { - MFEM_VERIFY(lumped_port_init, - "Port S-parameters not defined until ports are initialized!"); - const LumpedPortData &data = lumped_port_op.GetPort(idx); - const LumpedPortData &src_data = lumped_port_op.GetPort(source_idx); - const auto it = lumped_port_vi.find(idx); - MFEM_VERIFY(src_data.excitation, - "Lumped port index " << source_idx << " is not marked for excitation!"); - MFEM_VERIFY(it != lumped_port_vi.end(), - "Could not find lumped port when calculating port S-parameters!"); - std::complex S_ij = it->second.S; - if (idx == source_idx) + ValidateDoPortMeasurement(); + // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx + // TODO: Merge lumped and wave port S_ij calcluations to allow both at same time. + if (is_lumped_port) { - S_ij.real(S_ij.real() - 1.0); + const LumpedPortData &data = lumped_port_op->GetPort(idx); + const LumpedPortData &src_data = lumped_port_op->GetPort(source_idx); + const auto it = measurment_cache.lumped_port_vi->find(idx); + MFEM_VERIFY(src_data.excitation, + "Lumped port index " << source_idx << " is not marked for excitation!"); + MFEM_VERIFY(it != measurment_cache.lumped_port_vi->end(), + "Could not find lumped port when calculating port S-parameters!"); + std::complex S_ij = it->second.S; + if (idx == source_idx) + { + S_ij.real(S_ij.real() - 1.0); + } + // Generalized S-parameters if the ports are resistive (avoids divide-by-zero). + if (std::abs(data.R) > 0.0) + { + S_ij *= std::sqrt(src_data.R / data.R); + } + return S_ij; } - // Generalized S-parameters if the ports are resistive (avoids divide-by-zero). - if (std::abs(data.R) > 0.0) + else { - S_ij *= std::sqrt(src_data.R / data.R); + // Wave port modes are not normalized to a characteristic impedance so no generalized + // S-parameters are available. + const WavePortData &data = wave_port_op->GetPort(idx); + const WavePortData &src_data = wave_port_op->GetPort(source_idx); + const auto it = measurment_cache.wave_port_vi->find(idx); + MFEM_VERIFY(src_data.excitation, + "Wave port index " << source_idx << " is not marked for excitation!"); + MFEM_VERIFY(it != measurment_cache.wave_port_vi->end(), + "Could not find wave port when calculating port S-parameters!"); + std::complex S_ij = it->second.S; + if (idx == source_idx) + { + S_ij.real(S_ij.real() - 1.0); + } + // Port de-embedding: S_demb = S exp(ikₙᵢ dᵢ) exp(ikₙⱼ dⱼ) (distance offset is default 0 + // unless specified). + S_ij *= std::exp(1i * src_data.kn0 * src_data.d_offset); + S_ij *= std::exp(1i * data.kn0 * data.d_offset); + return S_ij; } - return S_ij; } -std::complex PostOperator::GetSParameter(const WavePortOperator &wave_port_op, - int idx, int source_idx) const +std::complex PostOperator::GetPortPower(int idx) const { - // Wave port modes are not normalized to a characteristic impedance so no generalized - // S-parameters are available. - MFEM_VERIFY(wave_port_init, "Port S-parameters not defined until ports are initialized!"); - const WavePortData &data = wave_port_op.GetPort(idx); - const WavePortData &src_data = wave_port_op.GetPort(source_idx); - const auto it = wave_port_vi.find(idx); - MFEM_VERIFY(src_data.excitation, - "Wave port index " << source_idx << " is not marked for excitation!"); - MFEM_VERIFY(it != wave_port_vi.end(), - "Could not find wave port when calculating port S-parameters!"); - std::complex S_ij = it->second.S; - if (idx == source_idx) + ValidateDoPortMeasurement(); + // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx + auto it_lumped = measurment_cache.lumped_port_vi->find(idx); + if (it_lumped != measurment_cache.lumped_port_vi->end()) { - S_ij.real(S_ij.real() - 1.0); + return it_lumped->second.P; } - // Port de-embedding: S_demb = S exp(ikₙᵢ dᵢ) exp(ikₙⱼ dⱼ) (distance offset is default 0 - // unless specified). - S_ij *= std::exp(1i * src_data.kn0 * src_data.d_offset); - S_ij *= std::exp(1i * data.kn0 * data.d_offset); - return S_ij; -} - -std::complex PostOperator::GetPortPower(const LumpedPortOperator &lumped_port_op, - int idx) const -{ - MFEM_VERIFY(lumped_port_init, - "Lumped port quantities not defined until ports are initialized!"); - const auto it = lumped_port_vi.find(idx); - MFEM_VERIFY(it != lumped_port_vi.end(), - "Could not find lumped port when calculating lumped port power!"); - return it->second.P; -} - -std::complex PostOperator::GetPortPower(const WavePortOperator &wave_port_op, - int idx) const -{ - MFEM_VERIFY(wave_port_init, - "Wave port quantities not defined until ports are initialized!"); - const auto it = wave_port_vi.find(idx); - MFEM_VERIFY(it != wave_port_vi.end(), - "Could not find wave port when calculating wave port power!"); - return it->second.P; -} - -std::complex PostOperator::GetPortVoltage(const LumpedPortOperator &lumped_port_op, - int idx) const -{ - MFEM_VERIFY(lumped_port_init, - "Lumped port quantities not defined until ports are initialized!"); - const auto it = lumped_port_vi.find(idx); - MFEM_VERIFY(it != lumped_port_vi.end(), - "Could not find lumped port when calculating lumped port voltage!"); - return it->second.V; + auto it_wave = measurment_cache.wave_port_vi->find(idx); + if (it_wave != measurment_cache.wave_port_vi->end()) + { + return it_wave->second.P; + } + MFEM_ABORT( + fmt::format("Port Power: Could not find a lumped or wave port with index {}!", idx)); } -std::complex PostOperator::GetPortVoltage(const WavePortOperator &wave_port_op, - int idx) const +std::complex PostOperator::GetPortVoltage(int idx) const { - MFEM_ABORT("GetPortVoltage is not yet implemented for wave port boundaries!"); - return 0.0; + ValidateDoPortMeasurement(); + // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx + auto it_lumped = measurment_cache.lumped_port_vi->find(idx); + if (it_lumped != measurment_cache.lumped_port_vi->end()) + { + return it_lumped->second.V; + } + auto it_wave = measurment_cache.wave_port_vi->find(idx); + if (it_wave != measurment_cache.wave_port_vi->end()) + { + MFEM_ABORT("GetPortVoltage is not yet implemented for wave port boundaries!"); + } + MFEM_ABORT(fmt::format( + "Port Voltage: Could not find a lumped or wave port with index {}!", idx)); } -std::complex PostOperator::GetPortCurrent(const LumpedPortOperator &lumped_port_op, - int idx, +std::complex PostOperator::GetPortCurrent(int idx, LumpedPortData::Branch branch) const { - MFEM_VERIFY(lumped_port_init, - "Lumped port quantities not defined until ports are initialized!"); - const auto it = lumped_port_vi.find(idx); - MFEM_VERIFY(it != lumped_port_vi.end(), - "Could not find lumped port when calculating lumped port current!"); - return ((branch == LumpedPortData::Branch::TOTAL || branch == LumpedPortData::Branch::R) - ? it->second.I[0] - : 0.0) + - ((branch == LumpedPortData::Branch::TOTAL || branch == LumpedPortData::Branch::L) - ? it->second.I[1] - : 0.0) + - ((branch == LumpedPortData::Branch::TOTAL || branch == LumpedPortData::Branch::C) - ? it->second.I[2] - : 0.0); -} - -std::complex PostOperator::GetPortCurrent(const WavePortOperator &wave_port_op, - int idx) const -{ - MFEM_ABORT("GetPortCurrent is not yet implemented for wave port boundaries!"); - return 0.0; + ValidateDoPortMeasurement(); + // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx + auto it_lumped = measurment_cache.lumped_port_vi->find(idx); + if (it_lumped != measurment_cache.lumped_port_vi->end()) + { + auto &I_loc = it_lumped->second.I; + switch (branch) + { + case LumpedPortData::Branch::R: + return I_loc[0]; + case LumpedPortData::Branch::L: + return I_loc[1]; + case LumpedPortData::Branch::C: + return I_loc[2]; + default: + return std::accumulate(I_loc.begin(), I_loc.end(), std::complex{0.0, 0.0}); + } + } + auto it_wave = measurment_cache.wave_port_vi->find(idx); + if (it_wave != measurment_cache.wave_port_vi->end()) + { + MFEM_ABORT("GetPortCurrent is not yet implemented for wave port boundaries!"); + } + MFEM_ABORT(fmt::format( + "Port Current: Could not find a lumped or wave port with index {}!", idx)); } -double PostOperator::GetInductorParticipation(const LumpedPortOperator &lumped_port_op, - int idx, double E_m) const +double PostOperator::GetInductorParticipation(int idx, double E_m) const { // Compute energy-participation ratio of junction given by index idx for the field mode. // We first get the port line voltage, and use lumped port circuit impedance to get peak @@ -638,25 +917,30 @@ double PostOperator::GetInductorParticipation(const LumpedPortOperator &lumped_p // p_mj = 1/2 L_j I_mj² / E_m. // An element with no assigned inductance will be treated as having zero admittance and // thus zero current. - const LumpedPortData &data = lumped_port_op.GetPort(idx); - std::complex I_mj = - GetPortCurrent(lumped_port_op, idx, LumpedPortData::Branch::L); + if (lumped_port_op == nullptr) + { + return 0.0; + } + const LumpedPortData &data = lumped_port_op->GetPort(idx); + std::complex I_mj = GetPortCurrent(idx, LumpedPortData::Branch::L); return std::copysign(0.5 * std::abs(data.L) * std::real(I_mj * std::conj(I_mj)) / E_m, I_mj.real()); // mean(I²) = (I_r² + I_i²) / 2 } -double PostOperator::GetExternalKappa(const LumpedPortOperator &lumped_port_op, int idx, - double E_m) const +double PostOperator::GetExternalKappa(int idx, double E_m) const { - // Compute participation ratio of external ports (given as any port boundary with nonzero - // resistance). Currently no reactance of the ports is supported. The κ of the port - // follows from: + // Compute participation ratio of external ports (given as any port boundary with + // nonzero resistance). Currently no reactance of the ports is supported. The κ of the + // port follows from: // κ_mj = 1/2 R_j I_mj² / E_m // from which the mode coupling quality factor is computed as: // Q_mj = ω_m / κ_mj. - const LumpedPortData &data = lumped_port_op.GetPort(idx); - std::complex I_mj = - GetPortCurrent(lumped_port_op, idx, LumpedPortData::Branch::R); + if (lumped_port_op == nullptr) + { + return 0.0; + } + const LumpedPortData &data = lumped_port_op->GetPort(idx); + std::complex I_mj = GetPortCurrent(idx, LumpedPortData::Branch::R); return std::copysign(0.5 * std::abs(data.R) * std::real(I_mj * std::conj(I_mj)) / E_m, I_mj.real()); // mean(I²) = (I_r² + I_i²) / 2 } @@ -811,14 +1095,35 @@ void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const std::vector> PostOperator::ProbeEField() const { - MFEM_VERIFY(E, "PostOperator is not configured for electric field probes!"); - return interp_op.ProbeField(*E); + if (!measurment_cache.probe_E_field.has_value()) + { + MFEM_VERIFY(E, "PostOperator is not configured for electric field probes!"); + if (interp_op.GetProbes().size() > 0) + { + measurment_cache.probe_E_field = interp_op.ProbeField(*E); + } + else + { + measurment_cache.probe_E_field.emplace(); + } + } + return *measurment_cache.probe_E_field; } - std::vector> PostOperator::ProbeBField() const { - MFEM_VERIFY(B, "PostOperator is not configured for magnetic flux density probes!"); - return interp_op.ProbeField(*B); + if (!measurment_cache.probe_B_field.has_value()) + { + MFEM_VERIFY(B, "PostOperator is not configured for magnetic flux density probes!"); + if (interp_op.GetProbes().size() > 0) + { + measurment_cache.probe_B_field = interp_op.ProbeField(*B); + } + else + { + measurment_cache.probe_B_field.emplace(); + } + } + return *measurment_cache.probe_B_field; } } // namespace palace diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index b21e79fb0..41e0d95c2 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -39,15 +39,26 @@ class PostOperator // Reference to material property operator (not owned). const MaterialOperator &mat_op; - // Surface boundary and domain postprocessors. - const SurfacePostOperator surf_post_op; - const DomainPostOperator dom_post_op; - // Objects for grid function postprocessing from the FE solution. - mutable std::unique_ptr E, B, V, A; + std::unique_ptr E, B, V, A; std::unique_ptr S, E_sr, E_si, B_sr, B_si, A_s, J_sr, J_si; std::unique_ptr U_e, U_m, V_s, Q_sr, Q_si; + // Data collection for writing fields to disk for visualization + // Paraview fields are mutable as the writing is triggered by const solver printer + mutable mfem::ParaViewDataCollection paraview, paraview_bdr; + double mesh_Lc0; + + // ----- Measurements from Fields ----- + + DomainPostOperator dom_post_op; // Energy in bulk + SurfacePostOperator surf_post_op; // Dielectric Interface Energy and Flux + mutable InterpolationOperator interp_op; // E & B fields: mutates during measure + + // Port Contributions: not owned, view onto space_op only, it must not go out of scope + LumpedPortOperator *lumped_port_op = nullptr; + WavePortOperator *wave_port_op = nullptr; + // Wave port boundary mode field postprocessing. struct WavePortFieldData { @@ -55,19 +66,56 @@ class PostOperator }; std::map port_E0; - // Lumped and wave port voltage and current (R, L, and C branches) caches updated when - // the grid functions are set. +public: + // Mini storage clases for cache: Definitions are public + struct FluxData + { + int idx; // Surface index + std::complex Phi; // Integrated flux + SurfaceFluxType type; // Flux type + }; + + struct InterfaceData + { + int idx; // Interface index + double energy; // Surface ELectric Field Energy + double tandelta; // Dissipation tangent tan(δ) + }; + + // For both lumped and wave port struct PortPostData { - std::complex P, V, I[3], S; + std::complex P, V, S; + std::array, 3> I; // Separate R, L, and C branches }; - std::map lumped_port_vi, wave_port_vi; - bool lumped_port_init, wave_port_init; - // Data collection for writing fields to disk for visualization and sampling points. - mutable mfem::ParaViewDataCollection paraview, paraview_bdr; - mutable InterpolationOperator interp_op; - double mesh_Lc0; +private: + struct MeasurementCache + { + std::optional> omega = std::nullopt; + + std::optional domain_E_field_energy_all = std::nullopt; + std::optional domain_H_field_energy_all = std::nullopt; + + std::optional> domain_E_field_energy_i = std::nullopt; + std::optional> domain_H_field_energy_i = std::nullopt; + + std::optional> surface_flux_i = std::nullopt; + std::optional> interface_eps_i = std::nullopt; + + std::optional> lumped_port_vi = std::nullopt; + std::optional> wave_port_vi = std::nullopt; + + std::optional lumped_port_inductor_energy = std::nullopt; + std::optional lumped_port_capacitor_energy = std::nullopt; + + std::optional>> probe_E_field = std::nullopt; + std::optional>> probe_B_field = std::nullopt; + }; + mutable MeasurementCache measurment_cache = {}; + + void ValidateDoPortMeasurement() const; + void InitializeDataCollection(const IoData &iodata); public: @@ -118,6 +166,22 @@ class PostOperator return *A; } + // Function that triggers all available post-processing measurements and populate cache. + void MeasureAll(); + + // Clear internal measurement caches + void ClearAllMeasurementCache(); + + // Treat the frequency, for driven and eigemode solvers, as a "measurement", that other + // measurements can depend on. This has to be supplied during the solver loop separate + // from the fields. + void SetFrequency(double omega); + void SetFrequency(std::complex omega); + + // Return stored frequency that was given in SetFrequency. Always promotes to complex + // frequency. + std::complex GetFrequency() const; + // Postprocess the total electric and magnetic field energies in the electric and magnetic // fields. double GetEFieldEnergy() const; @@ -130,55 +194,42 @@ class PostOperator // Postprocess the electric or magnetic field flux for a surface index using the computed // electcric field and/or magnetic flux density field solutions. - std::complex GetSurfaceFlux(int idx) const; + std::vector GetSurfaceFluxAll() const; + FluxData GetSurfaceFlux(int idx) const; // Postprocess the partitipation ratio for interface lossy dielectric losses in the // electric field mode. double GetInterfaceParticipation(int idx, double E_m) const; + std::vector GetInterfaceEFieldEnergyAll() const; + InterfaceData GetInterfaceEFieldEnergy(int idx) const; - // Update cached port voltages and currents for lumped and wave port operators. - void UpdatePorts(const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, double omega = 0.0) - { - UpdatePorts(lumped_port_op, omega); - UpdatePorts(wave_port_op, omega); - } - void UpdatePorts(const LumpedPortOperator &lumped_port_op, double omega = 0.0); - void UpdatePorts(const WavePortOperator &wave_port_op, double omega = 0.0); + // Measure and cache port voltages and currents for lumped and wave port operators. + void MeasureLumpedPorts() const; + void MeasureWavePorts() const; // Postprocess the energy in lumped capacitor or inductor port boundaries with index in // the provided set. - double GetLumpedInductorEnergy(const LumpedPortOperator &lumped_port_op) const; - double GetLumpedCapacitorEnergy(const LumpedPortOperator &lumped_port_op) const; + double GetLumpedInductorEnergy() const; + double GetLumpedCapacitorEnergy() const; // Postprocess the S-parameter for recieving lumped or wave port index using the electric // field solution. - std::complex GetSParameter(const LumpedPortOperator &lumped_port_op, int idx, - int source_idx) const; - std::complex GetSParameter(const WavePortOperator &wave_port_op, int idx, - int source_idx) const; + std::complex GetSParameter(bool is_lumped_port, int idx, int source_idx) const; // Postprocess the circuit voltage and current across lumped port index using the electric // field solution. When the internal grid functions are real-valued, the returned voltage // has only a nonzero real part. - std::complex GetPortPower(const LumpedPortOperator &lumped_port_op, - int idx) const; - std::complex GetPortPower(const WavePortOperator &wave_port_op, int idx) const; - std::complex GetPortVoltage(const LumpedPortOperator &lumped_port_op, - int idx) const; - std::complex GetPortVoltage(const WavePortOperator &wave_port_op, int idx) const; + std::complex GetPortPower(int idx) const; + std::complex GetPortVoltage(int idx) const; std::complex - GetPortCurrent(const LumpedPortOperator &lumped_port_op, int idx, + GetPortCurrent(int idx, LumpedPortData::Branch branch = LumpedPortData::Branch::TOTAL) const; - std::complex GetPortCurrent(const WavePortOperator &wave_port_op, int idx) const; // Postprocess the EPR for the electric field solution and lumped port index. - double GetInductorParticipation(const LumpedPortOperator &lumped_port_op, int idx, - double E_m) const; + double GetInductorParticipation(int idx, double E_m) const; // Postprocess the coupling rate for radiative loss to the given I-O port index. - double GetExternalKappa(const LumpedPortOperator &lumped_port_op, int idx, - double E_m) const; + double GetExternalKappa(int idx, double E_m) const; // Write to disk the E- and B-fields extracted from the solution vectors. Note that fields // are not redimensionalized, to do so one needs to compute: B <= B * (μ₀ H₀), E <= E * From 1b547e2f79e8b1650cf0534c60a37dbc3f023fb0 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:49 -0800 Subject: [PATCH 11/21] Add missing include --- palace/models/postoperator.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index 41e0d95c2..d750b30f4 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include From b01bee79ae07a4c8d4c0bb3a190e9e4e692b3af3 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:50 -0800 Subject: [PATCH 12/21] Fix table tests with changed default --- palace/utils/tablecsv.hpp | 6 +++--- test/unit/test-tablecsv.cpp | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/palace/utils/tablecsv.hpp b/palace/utils/tablecsv.hpp index e3ab6afc0..cfd4750b3 100644 --- a/palace/utils/tablecsv.hpp +++ b/palace/utils/tablecsv.hpp @@ -208,7 +208,7 @@ class Table auto to = [&buf](auto f, auto &&...a) { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; - for (int i = 0; i < n_cols(); i++) + for (size_t i = 0; i < n_cols(); i++) { if (i > 0) { @@ -225,7 +225,7 @@ class Table auto to = [&buf](auto f, auto &&...a) { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; - for (int i = 0; i < n_cols(); i++) + for (size_t i = 0; i < n_cols(); i++) { if (i > 0) { @@ -254,7 +254,7 @@ class Table { fmt::memory_buffer buf{}; append_header(buf); - for (int j = 0; j < n_rows(); j++) + for (size_t j = 0; j < n_rows(); j++) { append_row(buf, j); } diff --git a/test/unit/test-tablecsv.cpp b/test/unit/test-tablecsv.cpp index 44846e02d..763984cb4 100644 --- a/test/unit/test-tablecsv.cpp +++ b/test/unit/test-tablecsv.cpp @@ -74,9 +74,9 @@ TEST_CASE("TableCSV", "[tablecsv]") // clang-format off auto table_str1 = std::string( - " Header Col 1, Header Col 2, Header Col 3\n" - " NULL, +2.000000000e+00, +3.000000000e+00\n" - " NULL, NULL, +6.000000000e+00\n" + " Header Col 1, Header Col 2, Header Col 3\n" + " NULL, +2.000000000e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" ); // clang-format on CHECK(table.format_table() == table_str1); @@ -87,9 +87,9 @@ TEST_CASE("TableCSV", "[tablecsv]") // clang-format off auto table_str2 = std::string( - " Header Col 1, Header Col 2, Header Col 3\n" - " NULL, +2.000000e+00, +3.000000000e+00\n" - " NULL, NULL, +6.000000000e+00\n" + " Header Col 1, Header Col 2, Header Col 3\n" + " NULL, +2.000000e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" ); // clang-format on CHECK(table.format_table() == table_str2); @@ -101,9 +101,9 @@ TEST_CASE("TableCSV", "[tablecsv]") // clang-format off auto table_str3 = std::string( - " Header Col 1,Header Col 2, Header Col 3\n" - " NULL, 2.00e+00, +3.000000000e+00\n" - " NULL, NULL, +6.000000000e+00\n" + " Header Col 1,Header Col 2, Header Col 3\n" + " NULL, 2.00e+00, +3.000000000e+00\n" + " NULL, NULL, +6.000000000e+00\n" ); // clang-format on CHECK(table.format_table() == table_str3); From f1368c0c606083efc843aefd61f08c54c5ad4b27 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:50 -0800 Subject: [PATCH 13/21] Clang-tidy tweaks - remove redunant default init - init doubles in postop --- palace/drivers/basesolver.hpp | 12 ++++++------ palace/drivers/drivensolver.hpp | 8 ++++---- palace/drivers/eigensolver.hpp | 10 +++++----- palace/drivers/transientsolver.hpp | 6 +++--- palace/models/postoperator.cpp | 14 ++------------ palace/utils/tablecsv.hpp | 19 +++++++++---------- 6 files changed, 29 insertions(+), 40 deletions(-) diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index d951cebbc..370f33b02 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -40,7 +40,7 @@ class BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile domain_E = {}; + TableWithCSVFile domain_E; public: DomainsPostPrinter() = default; @@ -57,8 +57,8 @@ class BaseSolver bool root_ = false; bool do_measurement_flux_ = false; bool do_measurement_eps_ = false; - TableWithCSVFile surface_F = {}; - TableWithCSVFile surface_Q = {}; + TableWithCSVFile surface_F; + TableWithCSVFile surface_Q; public: SurfacesPostPrinter() = default; @@ -79,8 +79,8 @@ class BaseSolver bool root_ = false; bool do_measurement_E_ = false; bool do_measurement_B_ = false; - TableWithCSVFile probe_E = {}; - TableWithCSVFile probe_B = {}; + TableWithCSVFile probe_E; + TableWithCSVFile probe_B; int v_dim = 0; bool has_imag = false; @@ -106,7 +106,7 @@ class BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile error_indicator = {}; + TableWithCSVFile error_indicator; public: ErrorIndicatorPostPrinter() = default; diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index 1c8bd51f8..4fd3ab9bf 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -34,7 +34,7 @@ class DrivenSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile surface_I = {}; + TableWithCSVFile surface_I; public: CurrentsPostPrinter() = default; @@ -48,8 +48,8 @@ class DrivenSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile port_V = {}; - TableWithCSVFile port_I = {}; + TableWithCSVFile port_V; + TableWithCSVFile port_I; public: PortsPostPrinter() = default; @@ -68,7 +68,7 @@ class DrivenSolver : public BaseSolver bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile port_S = {}; + TableWithCSVFile port_S; // Currently can't mix lumped and sufrace ports for s-matrix bool src_lumped_port = true; diff --git a/palace/drivers/eigensolver.hpp b/palace/drivers/eigensolver.hpp index 66ab809bf..81fec562c 100644 --- a/palace/drivers/eigensolver.hpp +++ b/palace/drivers/eigensolver.hpp @@ -28,7 +28,7 @@ class EigenSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile eig = {}; + TableWithCSVFile eig; // Print data to stdout with custom table formatting void PrintStdoutHeader(); @@ -47,8 +47,8 @@ class EigenSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile port_V = {}; - TableWithCSVFile port_I = {}; + TableWithCSVFile port_V; + TableWithCSVFile port_I; public: PortsPostPrinter() = default; @@ -64,8 +64,8 @@ class EigenSolver : public BaseSolver bool root_ = false; bool do_measurement_EPR_ = false; bool do_measurement_Q_ = false; - TableWithCSVFile port_EPR = {}; - TableWithCSVFile port_Q = {}; + TableWithCSVFile port_EPR; + TableWithCSVFile port_Q; std::vector ports_with_L; std::vector ports_with_R; diff --git a/palace/drivers/transientsolver.hpp b/palace/drivers/transientsolver.hpp index 82d6edc97..ee28e0c38 100644 --- a/palace/drivers/transientsolver.hpp +++ b/palace/drivers/transientsolver.hpp @@ -33,7 +33,7 @@ class TransientSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile surface_I = {}; + TableWithCSVFile surface_I; public: CurrentsPostPrinter() = default; @@ -47,8 +47,8 @@ class TransientSolver : public BaseSolver { bool root_ = false; bool do_measurement_ = false; - TableWithCSVFile port_V = {}; - TableWithCSVFile port_I = {}; + TableWithCSVFile port_V; + TableWithCSVFile port_I; public: PortsPostPrinter() = default; diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 80b02819a..8b42e42e6 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -478,7 +478,7 @@ double PostOperator::GetEFieldEnergy(int idx) const measurment_cache.domain_E_field_energy_i.emplace(); for (const auto &[idx, data] : dom_post_op.M_i) { - double out; + double out = 0.0; // Defaults to zero: no failure if (V) { out = dom_post_op.GetDomainElectricFieldEnergy(idx, *V); @@ -487,11 +487,6 @@ double PostOperator::GetEFieldEnergy(int idx) const { out = dom_post_op.GetDomainElectricFieldEnergy(idx, *E); } - else - { - // No failure: returns zero - out = 0.0; - } measurment_cache.domain_E_field_energy_i->emplace(idx, out); } } @@ -511,7 +506,7 @@ double PostOperator::GetHFieldEnergy(int idx) const measurment_cache.domain_H_field_energy_i.emplace(); for (const auto &[idx, data] : dom_post_op.M_i) { - double out; + double out = 0.0; // Defaults to zero: no failure if (A) { out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *A); @@ -520,11 +515,6 @@ double PostOperator::GetHFieldEnergy(int idx) const { out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *B); } - else - { - // No failure: returns zero - out = 0.0; - } measurment_cache.domain_H_field_energy_i->emplace(idx, out); } } diff --git a/palace/utils/tablecsv.hpp b/palace/utils/tablecsv.hpp index cfd4750b3..874f80549 100644 --- a/palace/utils/tablecsv.hpp +++ b/palace/utils/tablecsv.hpp @@ -37,7 +37,7 @@ class Column // ---- // Map-like index, to interface via Table class - std::string name = ""; + std::string name; public: [[nodiscard]] size_t col_width() const @@ -85,13 +85,13 @@ class Column { } - std::vector data = {}; - std::string header_text = ""; + std::vector data; + std::string header_text; - std::optional min_left_padding = {}; - std::optional float_precision = {}; - std::optional empty_cell_val = {}; - std::optional fmt_sign = {}; + std::optional min_left_padding; + std::optional float_precision; + std::optional empty_cell_val; + std::optional fmt_sign; [[nodiscard]] size_t n_rows() const { return data.size(); } @@ -103,7 +103,7 @@ class Table { // Column-wise mini-table for storing data and and printing to csv file for doubles. // Future: allow int and other output, allow non-owning memeory via span - std::vector cols = {}; + std::vector cols; // Cache value to reserve vector space by default size_t reserve_n_rows = 0; @@ -201,7 +201,6 @@ class Table // Formatting and Printing Options // TODO: Improve all the functions below with ranges in C++20 -public: template void append_header(T &buf) const { @@ -266,7 +265,7 @@ class Table class TableWithCSVFile { - std::string csv_file_fullpath_ = ""; + std::string csv_file_fullpath_; unsigned long file_append_curser = -1; public: From 771b2859466639069ee41fdbcee12a02315e8c5b Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:50 -0800 Subject: [PATCH 14/21] Bugfix in eigenmode port_Q printer --- palace/drivers/eigensolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index 7f1611b17..e6ec2d61e 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -565,7 +565,7 @@ EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, port_Q = TableWithCSVFile(post_dir / "port-Q.csv"); port_Q.table.reserve(n_expected_rows, 1 + ports_with_R.size()); port_Q.table.insert_column(Column("idx", "m", 0, {}, {}, "")); - for (const auto idx : ports_with_L) + for (const auto idx : ports_with_R) { port_Q.table.insert_column(format("Ql_{}", idx), format("Q_ext[{}]", idx)); port_Q.table.insert_column(format("Kl_{}", idx), format("κ_ext[{}] (GHz)", idx)); From f15eeaf1335cb356050732d1631f0029afc5fc21 Mon Sep 17 00:00:00 2001 From: Philipp Dumitrescu Date: Tue, 21 Jan 2025 15:50:50 -0800 Subject: [PATCH 15/21] Fix bug in s-parameters printer --- palace/drivers/drivensolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index 2ce50e37c..b015ba734 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -620,7 +620,7 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( std::complex S_ij = post_op.GetSParameter(src_lumped_port, o_idx, source_idx); auto abs_S_ij = 20.0 * std::log10(std::abs(S_ij)); - auto arg_S_ij = std::arg(S_ij) * 180.8 / M_PI; + auto arg_S_ij = std::arg(S_ij) * 180.0 / M_PI; port_S.table[format("abs_{}_{}", o_idx, source_idx)] << abs_S_ij; port_S.table[format("arg_{}_{}", o_idx, source_idx)] << arg_S_ij; From a24071fada1d49d9d653c81c3aa77a38e9114b1b Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:50 -0800 Subject: [PATCH 16/21] Use variadic method to remove method --- palace/utils/tablecsv.hpp | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/palace/utils/tablecsv.hpp b/palace/utils/tablecsv.hpp index 874f80549..f99bd3df3 100644 --- a/palace/utils/tablecsv.hpp +++ b/palace/utils/tablecsv.hpp @@ -47,7 +47,7 @@ class Column // Normal float in our exponent format needs float_precision + 7 ("+" , leading digit, // ".", "e", "+", +2 exponent. Sometimes exponent maybe +3 if very small or large; see - // std::numeric_limits::max_exponent. We pick +7 for consistnacy, but + // std::numeric_limits::max_exponent. We pick +7 for consistency, but // min_left_padding should be at least 1, which is not currently enforced. return std::max(pad + prec + 7, header_text.size()); } @@ -142,23 +142,6 @@ class Table } // Insert columns: map like interface - - bool insert_column(std::string column_name, std::string column_header = "") - { - auto it = std::find_if(cols.begin(), cols.end(), - [&column_name](auto &c) { return c.name == column_name; }); - if (it != cols.end()) - { - return false; - } - auto &col = cols.emplace_back(std::move(column_name), std::move(column_header)); - col.defaults = &col_options; - if (reserve_n_rows > 0) - { - col.data.reserve(reserve_n_rows); - } - return true; - } bool insert_column(Column &&column) { auto it = std::find_if(cols.begin(), cols.end(), @@ -175,6 +158,11 @@ class Table } return true; } + template + bool insert_column(Args &&...args) + { + return insert_column(Column(std::forward(args)...)); + } // Access columns via vector position or column name From 94240c15cebd0254f0861a3809c9cb66404fd0bb Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:51 -0800 Subject: [PATCH 17/21] Simplify PostOperator to remove mutability of cache and optional --- palace/drivers/basesolver.cpp | 3 +- palace/drivers/drivensolver.cpp | 8 +- palace/drivers/drivensolver.hpp | 2 +- palace/drivers/eigensolver.cpp | 7 +- palace/drivers/transientsolver.cpp | 2 +- palace/models/postoperator.cpp | 571 ++++++++++------------------- palace/models/postoperator.hpp | 80 ++-- 7 files changed, 253 insertions(+), 420 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index b045c774b..f43f467aa 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -15,7 +15,6 @@ #include "linalg/ksp.hpp" #include "models/domainpostoperator.hpp" #include "models/postoperator.hpp" -#include "models/spaceoperator.hpp" #include "models/surfacepostoperator.hpp" #include "utils/communication.hpp" #include "utils/dorfler.hpp" @@ -497,7 +496,7 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementEps(double idx_value_dimensi using fmt::format; // Interface Participation adds energy contriutions E_elec + E_cap - // E_cap returns zero if the solver does not supprot lumped ports. + // E_cap returns zero if the solver does not support lumped ports. double E_elec = post_op.GetEFieldEnergy() + post_op.GetLumpedCapacitorEnergy(); auto eps_data_vec = post_op.GetInterfaceEFieldEnergyAll(); diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index b015ba734..3725b5c7f 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -188,7 +188,7 @@ ErrorIndicator DrivenSolver::SweepUniform(SpaceOperator &space_op, PostOperator post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); post_op.SetFrequency(omega); - post_op.MeasureAll(); + post_op.MeasureAll(space_op); Mpi::Print(" Sol. ||E|| = {:.6e} (||RHS|| = {:.6e})\n", linalg::Norml2(space_op.GetComm(), E), @@ -367,7 +367,7 @@ ErrorIndicator DrivenSolver::SweepAdaptive(SpaceOperator &space_op, PostOperator post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); post_op.SetFrequency(omega); - post_op.MeasureAll(); + post_op.MeasureAll(space_op); Mpi::Print(" Sol. ||E|| = {:.6e}\n", linalg::Norml2(space_op.GetComm(), E)); @@ -617,7 +617,9 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( for (const auto o_idx : all_port_indices) { - std::complex S_ij = post_op.GetSParameter(src_lumped_port, o_idx, source_idx); + std::complex S_ij = + src_lumped_port ? post_op.GetSParameter(lumped_port_op, o_idx, source_idx) + : post_op.GetSParameter(wave_port_op, o_idx, source_idx); auto abs_S_ij = 20.0 * std::log10(std::abs(S_ij)); auto arg_S_ij = std::arg(S_ij) * 180.0 / M_PI; diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index 4fd3ab9bf..d51bc1230 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -70,7 +70,7 @@ class DrivenSolver : public BaseSolver bool do_measurement_ = false; TableWithCSVFile port_S; - // Currently can't mix lumped and sufrace ports for s-matrix + // Currently can't mix lumped and surface ports for s-matrix bool src_lumped_port = true; int source_idx = -1; diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index e6ec2d61e..faecd7ea1 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -316,7 +316,7 @@ EigenSolver::Solve(const std::vector> &mesh) const post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); post_op.SetFrequency(omega); - post_op.MeasureAll(); + post_op.MeasureAll(space_op); const double E_elec = post_op.GetEFieldEnergy(); const double E_mag = post_op.GetHFieldEnergy(); @@ -591,7 +591,8 @@ void EigenSolver::EPRPostPrinter::AddMeasurementEPR( port_EPR.table["idx"] << eigen_print_idx; for (const auto idx : ports_with_L) { - port_EPR.table[format("p_{}", idx)] << post_op.GetInductorParticipation(idx, E_m); + port_EPR.table[format("p_{}", idx)] + << post_op.GetInductorParticipation(lumped_port_op, idx, E_m); } port_EPR.AppendRow(); } @@ -616,7 +617,7 @@ void EigenSolver::EPRPostPrinter::AddMeasurementQ(double eigen_print_idx, port_EPR.table["idx"] << eigen_print_idx; for (const auto idx : ports_with_R) { - double Kl = post_op.GetExternalKappa(idx, E_m); + double Kl = post_op.GetExternalKappa(lumped_port_op, idx, E_m); double Ql = (Kl == 0.0) ? mfem::infinity() : omega.real() / std::abs(Kl); port_Q.table[format("Ql_{}", idx)] << Ql; diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index aafbba48b..b1f15d2de 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -109,7 +109,7 @@ TransientSolver::Solve(const std::vector> &mesh) const const Vector &B = time_op.GetB(); post_op.SetEGridFunction(E); post_op.SetBGridFunction(B); - post_op.MeasureAll(); + post_op.MeasureAll(space_op); const double E_elec = post_op.GetEFieldEnergy(); const double E_mag = post_op.GetHFieldEnergy(); diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 8b42e42e6..b889adf6b 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -38,8 +38,7 @@ PostOperator::PostOperator(const IoData &iodata, SpaceOperator &space_op, surf_post_op(iodata, space_op.GetMaterialOp(), space_op.GetH1Space()), dom_post_op(iodata, space_op.GetMaterialOp(), space_op.GetNDSpace(), space_op.GetRTSpace()), - interp_op(iodata, space_op.GetNDSpace()), lumped_port_op(&(space_op.GetLumpedPortOp())), - wave_port_op(&(space_op.GetWavePortOp())), + interp_op(iodata, space_op.GetNDSpace()), E(std::make_unique(space_op.GetNDSpace(), iodata.problem.type != config::ProblemData::Type::TRANSIENT)), @@ -347,260 +346,158 @@ void PostOperator::SetAGridFunction(const Vector &a, bool exchange_face_nbr_data void PostOperator::ClearAllMeasurementCache() { // Clear Cache: Save omega since this set by hand like fields E,... - bool has_omega = measurment_cache.omega.has_value(); - std::complex omega; - if (has_omega) - { - omega = *measurment_cache.omega; - } - - measurment_cache = {}; - - if (has_omega) - { - measurment_cache.omega = omega; - } + auto omega = measurement_cache.omega; + measurement_cache = {}; + measurement_cache.omega = omega; } void PostOperator::MeasureAll() { ClearAllMeasurementCache(); - // Domain Energy: Electric Field Contribution - if (V || E) - { - GetEFieldEnergy(); // if (dom_post_op.M_elec) - if (dom_post_op.M_i.size() > 0) - { - GetEFieldEnergy(dom_post_op.M_i.begin()->first); // Measures all domains - } - } - // Domain Energy: Magnetic Field Contribution - if (A || B) - { - GetHFieldEnergy(); // if (dom_post_op.M_mag) - if (dom_post_op.M_i.size() > 0) - { - GetHFieldEnergy(dom_post_op.M_i.begin()->first); // Measures all domains - } - } + MeasureEFieldEnergy(); + MeasureHFieldEnergy(); - if (E || B) - { - GetSurfaceFluxAll(); - } - if (E) - { - GetInterfaceEFieldEnergyAll(); - ProbeEField(); - } - if (B) - { - ProbeBField(); - } - if (E && B) - { - if (lumped_port_op != nullptr) - { - MeasureLumpedPorts(); - } - if (wave_port_op != nullptr) - { - MeasureWavePorts(); - } - } + MeasureSurfaceFlux(); + MeasureInterfaceEFieldEnergy(); + MeasureProbes(); +} + +void PostOperator::MeasureAll(const SpaceOperator &space_op) +{ + MeasureAll(); + MeasureLumpedPorts(space_op.GetLumpedPortOp()); + MeasureWavePorts(space_op.GetWavePortOp()); } void PostOperator::SetFrequency(double omega) { - measurment_cache.omega = std::complex(omega); + measurement_cache.omega = std::complex(omega); } void PostOperator::SetFrequency(std::complex omega) { - measurment_cache.omega = omega; + measurement_cache.omega = omega; } std::complex PostOperator::GetFrequency() const { - MFEM_VERIFY(measurment_cache.omega.has_value(), - "Frequency value omega has not been correctly set!"); - return *measurment_cache.omega; + return measurement_cache.omega; } double PostOperator::GetEFieldEnergy() const { - if (!measurment_cache.domain_E_field_energy_all.has_value()) - { - if (V) - { - measurment_cache.domain_E_field_energy_all = dom_post_op.GetElectricFieldEnergy(*V); - } - else if (E) - { - measurment_cache.domain_E_field_energy_all = dom_post_op.GetElectricFieldEnergy(*E); - } - else - { - // No failure: returns zero - measurment_cache.domain_E_field_energy_all = 0.0; - } - } - return *measurment_cache.domain_E_field_energy_all; + return measurement_cache.domain_E_field_energy_all; } -double PostOperator::GetHFieldEnergy() const +double PostOperator::GetEFieldEnergy(int idx) const { - if (!measurment_cache.domain_H_field_energy_all.has_value()) + auto it = measurement_cache.domain_E_field_energy_i.find(idx); + if (it == measurement_cache.domain_E_field_energy_i.end()) { - if (A) - { - measurment_cache.domain_H_field_energy_all = dom_post_op.GetMagneticFieldEnergy(*A); - } - else if (B) - { - measurment_cache.domain_H_field_energy_all = dom_post_op.GetMagneticFieldEnergy(*B); - } - else - { - // No failure: returns zero - measurment_cache.domain_H_field_energy_all = 0.0; - } + MFEM_ABORT(fmt::format("Could not find domain index {} for E field energy!", idx)); } - return *measurment_cache.domain_H_field_energy_all; + return it->second; } -double PostOperator::GetEFieldEnergy(int idx) const +void PostOperator::MeasureEFieldEnergy() { - if (!measurment_cache.domain_E_field_energy_i.has_value()) + measurement_cache.domain_E_field_energy_i.clear(); + for (const auto &[idx, data] : dom_post_op.M_i) { - // Do all measurements - measurment_cache.domain_E_field_energy_i.emplace(); - for (const auto &[idx, data] : dom_post_op.M_i) - { - double out = 0.0; // Defaults to zero: no failure - if (V) - { - out = dom_post_op.GetDomainElectricFieldEnergy(idx, *V); - } - else if (E) - { - out = dom_post_op.GetDomainElectricFieldEnergy(idx, *E); - } - measurment_cache.domain_E_field_energy_i->emplace(idx, out); - } + measurement_cache.domain_E_field_energy_i.emplace( + idx, (!V && !E) ? 0.0 : dom_post_op.GetDomainElectricFieldEnergy(idx, V ? *V : *E)); } - auto it = measurment_cache.domain_E_field_energy_i->find(idx); - if (it == measurment_cache.domain_E_field_energy_i->end()) + + measurement_cache.domain_E_field_energy_all = + (!V && !E) ? 0.0 : dom_post_op.GetElectricFieldEnergy(V ? *V : *E); +} + +void PostOperator::MeasureHFieldEnergy() +{ + measurement_cache.domain_H_field_energy_i.clear(); + for (const auto &[idx, data] : dom_post_op.M_i) { - MFEM_ABORT(fmt::format("Could not find domain index {} for E field energy!", idx)); + measurement_cache.domain_H_field_energy_i[idx] = + (!A && !B) ? 0.0 : dom_post_op.GetDomainMagneticFieldEnergy(idx, A ? *A : *B); } - return it->second; + + measurement_cache.domain_H_field_energy_all = + (!A && !B) ? 0.0 : dom_post_op.GetMagneticFieldEnergy(A ? *A : *B); +} + +double PostOperator::GetHFieldEnergy() const +{ + return measurement_cache.domain_H_field_energy_all; } double PostOperator::GetHFieldEnergy(int idx) const { - if (!measurment_cache.domain_H_field_energy_i.has_value()) - { - // Do all measurements - measurment_cache.domain_H_field_energy_i.emplace(); - for (const auto &[idx, data] : dom_post_op.M_i) - { - double out = 0.0; // Defaults to zero: no failure - if (A) - { - out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *A); - } - else if (B) - { - out = dom_post_op.GetDomainMagneticFieldEnergy(idx, *B); - } - measurment_cache.domain_H_field_energy_i->emplace(idx, out); - } - } - auto it = measurment_cache.domain_H_field_energy_i->find(idx); - if (it == measurment_cache.domain_H_field_energy_i->end()) + auto it = measurement_cache.domain_H_field_energy_i.find(idx); + if (it == measurement_cache.domain_H_field_energy_i.end()) { MFEM_ABORT(fmt::format("Could not find domain index {} for H field energy!", idx)); } return it->second; } -// Code Note: for returning the full vector we chose name GetSurfaceFluxAll() rather than -// GetSurfaceFlux(), to keep consistancy with GetEFieldEnergy(). GetEFieldEnergy() returns -// the total energy (calculated differently), not the vector of the individual -// GetHFieldEnergy(int idx). -std::vector PostOperator::GetSurfaceFluxAll() const +void PostOperator::MeasureSurfaceFlux() { // Compute the flux through a surface as Φ_j = ∫ F ⋅ n_j dS, with F = B, F = ε D, or F = // E x H. The special coefficient is used to avoid issues evaluating MFEM GridFunctions // which are discontinuous at interior boundary elements. - if (!measurment_cache.surface_flux_i.has_value()) + measurement_cache.surface_flux_i.clear(); + if (!E && !B) { - // Do all measurements - MFEM_VERIFY( - E || B, - "PostOperator needs either electric or magnetic field for flux calculation!"); - measurment_cache.surface_flux_i.emplace(); - measurment_cache.surface_flux_i->reserve(surf_post_op.flux_surfs.size()); - for (const auto &[idx, data] : surf_post_op.flux_surfs) - { - measurment_cache.surface_flux_i->emplace_back( - FluxData{idx, surf_post_op.GetSurfaceFlux(idx, E.get(), B.get()), data.type}); - } + return; + } + measurement_cache.surface_flux_i.reserve(surf_post_op.flux_surfs.size()); + for (const auto &[idx, data] : surf_post_op.flux_surfs) + { + measurement_cache.surface_flux_i.emplace_back( + FluxData{idx, surf_post_op.GetSurfaceFlux(idx, E.get(), B.get()), data.type}); } - return *measurment_cache.surface_flux_i; } PostOperator::FluxData PostOperator::GetSurfaceFlux(int idx) const { - if (!measurment_cache.surface_flux_i.has_value()) - { - GetSurfaceFluxAll(); - } - auto it = std::find_if(measurment_cache.surface_flux_i->begin(), - measurment_cache.surface_flux_i->end(), + auto it = std::find_if(measurement_cache.surface_flux_i.begin(), + measurement_cache.surface_flux_i.end(), [idx](const auto &d) { return d.idx == idx; }); - if (it == measurment_cache.surface_flux_i->end()) + if (it == measurement_cache.surface_flux_i.end()) { MFEM_ABORT(fmt::format("Could not find surface index {} for flux!", idx)); } return *it; } -std::vector PostOperator::GetInterfaceEFieldEnergyAll() const +void PostOperator::MeasureInterfaceEFieldEnergy() { // Compute the surface dielectric participation ratio and associated quality factor for // the material interface given by index idx. We have: // 1/Q_mj = p_mj tan(δ)_j // with: // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). - if (!measurment_cache.interface_eps_i.has_value()) + measurement_cache.interface_eps_i.clear(); + if (!E) { - // Do all measurements - MFEM_VERIFY(E, "Electric field solution required for E field interface energy!"); - measurment_cache.interface_eps_i.emplace(); - measurment_cache.interface_eps_i->reserve(surf_post_op.eps_surfs.size()); - for (const auto &[idx, data] : surf_post_op.eps_surfs) - { - measurment_cache.interface_eps_i->emplace_back( - InterfaceData{idx, surf_post_op.GetInterfaceElectricFieldEnergy(idx, *E), - surf_post_op.GetInterfaceLossTangent(idx)}); - } + return; + } + measurement_cache.interface_eps_i.reserve(surf_post_op.eps_surfs.size()); + for (const auto &[idx, data] : surf_post_op.eps_surfs) + { + measurement_cache.interface_eps_i.emplace_back( + InterfaceData{idx, surf_post_op.GetInterfaceElectricFieldEnergy(idx, *E), + surf_post_op.GetInterfaceLossTangent(idx)}); } - return *measurment_cache.interface_eps_i; } -PostOperator::InterfaceData PostOperator::GetInterfaceEFieldEnergy(int idx) const +const PostOperator::InterfaceData &PostOperator::GetInterfaceEFieldEnergy(int idx) const { - if (!measurment_cache.interface_eps_i.has_value()) - { - GetInterfaceEFieldEnergyAll(); - } - auto it = std::find_if(measurment_cache.interface_eps_i->begin(), - measurment_cache.interface_eps_i->end(), + auto it = std::find_if(measurement_cache.interface_eps_i.begin(), + measurement_cache.interface_eps_i.end(), [idx](const auto &d) { return d.idx == idx; }); - if (it == measurment_cache.interface_eps_i->end()) + if (it == measurement_cache.interface_eps_i.end()) { MFEM_ABORT(fmt::format("Could not find surface index {} for interface energy!", idx)); } @@ -619,21 +516,16 @@ double PostOperator::GetInterfaceParticipation(int idx, double E_m) const return data.energy / E_m; } -void PostOperator::MeasureLumpedPorts() const +void PostOperator::MeasureLumpedPorts(const LumpedPortOperator &lumped_port_op) { - MFEM_VERIFY(E && B && lumped_port_op != nullptr, - "Incorrect usage of PostOperator::MeasureLumpedPorts!"); - if (measurment_cache.lumped_port_vi.has_value()) - { - measurment_cache.lumped_port_vi->clear(); - } - else + measurement_cache.lumped_port_vi.clear(); + if (!E || !B) { - measurment_cache.lumped_port_vi.emplace(); + return; } - for (const auto &[idx, data] : *lumped_port_op) + for (const auto &[idx, data] : lumped_port_op) { - auto &vi = (*measurment_cache.lumped_port_vi)[idx]; + auto &vi = measurement_cache.lumped_port_vi[idx]; vi.P = data.GetPower(*E, *B); vi.V = data.GetVoltage(*E); if (HasImag()) @@ -666,37 +558,49 @@ void PostOperator::MeasureLumpedPorts() const vi.I[1] = vi.I[2] = vi.S = 0.0; } } -} -void PostOperator::MeasureWavePorts() const -{ - - MFEM_VERIFY(E && B && wave_port_op != nullptr, - "Incorrect usage of PostOperator::MeasureWavePorts!"); - if (measurment_cache.wave_port_vi.has_value()) + // Add contribution due to all inductive lumped boundaries in the model: + // E_ind = ∑_j 1/2 L_j I_mj². + measurement_cache.lumped_port_inductor_energy = 0.0; + for (const auto &[idx, data] : lumped_port_op) { - measurment_cache.wave_port_vi->clear(); + if (std::abs(data.L) > 0.0) + { + std::complex I_j = GetPortCurrent(idx, LumpedPortData::Branch::L); + measurement_cache.lumped_port_inductor_energy += + 0.5 * std::abs(data.L) * std::real(I_j * std::conj(I_j)); + } } - else + + // Add contribution due to all capacitive lumped boundaries in the model: + // E_cap = ∑_j 1/2 C_j V_mj². + measurement_cache.lumped_port_capacitor_energy = 0.0; + for (const auto &[idx, data] : lumped_port_op) { - measurment_cache.wave_port_vi.emplace(); + if (std::abs(data.C) > 0.0) + { + std::complex V_j = GetPortVoltage(idx); + measurement_cache.lumped_port_capacitor_energy += + 0.5 * std::abs(data.C) * std::real(V_j * std::conj(V_j)); + } } - if (!HasImag()) +} + +void PostOperator::MeasureWavePorts(const WavePortOperator &wave_port_op) +{ + measurement_cache.wave_port_vi.clear(); + // Wave ports need imaginary component. TODO: Fix this. + if (!E || !B || !HasImag()) { - return; // Wave ports need Imag; leave empty otherwise // TODO: Fix in long run + return; } - for (const auto &[idx, data] : *wave_port_op) + for (const auto &[idx, data] : wave_port_op) { - MFEM_VERIFY(measurment_cache.omega.has_value(), - "Measuring port currents with Imag fields, requires frequency to be set " - "with SetFrequency!"); - // Get value and make real: Matches current behaviour - auto omega = measurment_cache.omega->real(); - + auto omega = measurement_cache.omega.real(); MFEM_VERIFY(omega > 0.0, "Frequency domain wave port postprocessing requires nonzero frequency!"); - auto &vi = (*measurment_cache.wave_port_vi)[idx]; + auto &vi = measurement_cache.wave_port_vi[idx]; vi.P = data.GetPower(*E, *B); vi.S = data.GetSParameter(*E); vi.V = vi.I[0] = vi.I[1] = vi.I[2] = 0.0; // Not yet implemented @@ -704,144 +608,73 @@ void PostOperator::MeasureWavePorts() const } } -void PostOperator::ValidateDoPortMeasurement() const +double PostOperator::GetLumpedInductorEnergy() const { - if (!measurment_cache.lumped_port_vi.has_value()) - { - if (lumped_port_op != nullptr) - { - MeasureLumpedPorts(); - } - else - { - MFEM_ABORT("A lumped port measurement called, but the lumped port operator is not " - "defined by the solver.") - } - } - if (!measurment_cache.wave_port_vi.has_value()) - { - if (wave_port_op != nullptr) - { - MeasureWavePorts(); - } - else - { - MFEM_ABORT("A wave port measurement called, but the wave port operator is not " - "defined by the solver.") - } - } + return measurement_cache.lumped_port_inductor_energy; } -double PostOperator::GetLumpedInductorEnergy() const +double PostOperator::GetLumpedCapacitorEnergy() const { - // Add contribution due to all inductive lumped boundaries in the model: - // E_ind = ∑_j 1/2 L_j I_mj². - if (!measurment_cache.lumped_port_inductor_energy.has_value()) - { - // No failure if space has no lumped ports: Returns zero - double U = 0.0; - if (lumped_port_op != nullptr) - { - for (const auto &[idx, data] : *lumped_port_op) - { - if (std::abs(data.L) > 0.0) - { - std::complex I_j = GetPortCurrent(idx, LumpedPortData::Branch::L); - U += 0.5 * std::abs(data.L) * std::real(I_j * std::conj(I_j)); - } - } - } - measurment_cache.lumped_port_inductor_energy = U; - } - return *measurment_cache.lumped_port_inductor_energy; + return measurement_cache.lumped_port_capacitor_energy; } -double PostOperator::GetLumpedCapacitorEnergy() const +std::complex PostOperator::GetSParameter(const LumpedPortOperator &lumped_port_op, + int idx, int source_idx) const { - // Add contribution due to all capacitive lumped boundaries in the model: - // E_cap = ∑_j 1/2 C_j V_mj². - if (!measurment_cache.lumped_port_capacitor_energy.has_value()) + const LumpedPortData &data = lumped_port_op.GetPort(idx); + const LumpedPortData &src_data = lumped_port_op.GetPort(source_idx); + const auto it = measurement_cache.lumped_port_vi.find(idx); + MFEM_VERIFY(src_data.excitation, + "Lumped port index " << source_idx << " is not marked for excitation!"); + MFEM_VERIFY(it != measurement_cache.lumped_port_vi.end(), + "Could not find lumped port when calculating port S-parameters!"); + std::complex S_ij = it->second.S; + if (idx == source_idx) { - // No failure if space has no lumped ports: Returns zero - double U = 0.0; - if (lumped_port_op != nullptr) - { - for (const auto &[idx, data] : *lumped_port_op) - { - if (std::abs(data.C) > 0.0) - { - std::complex V_j = GetPortVoltage(idx); - U += 0.5 * std::abs(data.C) * std::real(V_j * std::conj(V_j)); - } - } - } - measurment_cache.lumped_port_capacitor_energy = U; + S_ij.real(S_ij.real() - 1.0); + } + // Generalized S-parameters if the ports are resistive (avoids divide-by-zero). + if (std::abs(data.R) > 0.0) + { + S_ij *= std::sqrt(src_data.R / data.R); } - return *measurment_cache.lumped_port_capacitor_energy; + return S_ij; } -std::complex PostOperator::GetSParameter(bool is_lumped_port, int idx, - int source_idx) const +std::complex PostOperator::GetSParameter(const WavePortOperator &wave_port_op, + int idx, int source_idx) const { - ValidateDoPortMeasurement(); - // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx - // TODO: Merge lumped and wave port S_ij calcluations to allow both at same time. - if (is_lumped_port) + // Wave port modes are not normalized to a characteristic impedance so no generalized + // S-parameters are available. + const WavePortData &data = wave_port_op.GetPort(idx); + const WavePortData &src_data = wave_port_op.GetPort(source_idx); + const auto it = measurement_cache.wave_port_vi.find(idx); + MFEM_VERIFY(src_data.excitation, + "Wave port index " << source_idx << " is not marked for excitation!"); + MFEM_VERIFY(it != measurement_cache.wave_port_vi.end(), + "Could not find wave port when calculating port S-parameters!"); + std::complex S_ij = it->second.S; + if (idx == source_idx) { - const LumpedPortData &data = lumped_port_op->GetPort(idx); - const LumpedPortData &src_data = lumped_port_op->GetPort(source_idx); - const auto it = measurment_cache.lumped_port_vi->find(idx); - MFEM_VERIFY(src_data.excitation, - "Lumped port index " << source_idx << " is not marked for excitation!"); - MFEM_VERIFY(it != measurment_cache.lumped_port_vi->end(), - "Could not find lumped port when calculating port S-parameters!"); - std::complex S_ij = it->second.S; - if (idx == source_idx) - { - S_ij.real(S_ij.real() - 1.0); - } - // Generalized S-parameters if the ports are resistive (avoids divide-by-zero). - if (std::abs(data.R) > 0.0) - { - S_ij *= std::sqrt(src_data.R / data.R); - } - return S_ij; - } - else - { - // Wave port modes are not normalized to a characteristic impedance so no generalized - // S-parameters are available. - const WavePortData &data = wave_port_op->GetPort(idx); - const WavePortData &src_data = wave_port_op->GetPort(source_idx); - const auto it = measurment_cache.wave_port_vi->find(idx); - MFEM_VERIFY(src_data.excitation, - "Wave port index " << source_idx << " is not marked for excitation!"); - MFEM_VERIFY(it != measurment_cache.wave_port_vi->end(), - "Could not find wave port when calculating port S-parameters!"); - std::complex S_ij = it->second.S; - if (idx == source_idx) - { - S_ij.real(S_ij.real() - 1.0); - } - // Port de-embedding: S_demb = S exp(ikₙᵢ dᵢ) exp(ikₙⱼ dⱼ) (distance offset is default 0 - // unless specified). - S_ij *= std::exp(1i * src_data.kn0 * src_data.d_offset); - S_ij *= std::exp(1i * data.kn0 * data.d_offset); - return S_ij; + S_ij.real(S_ij.real() - 1.0); } + // Port de-embedding: S_demb = S exp(ikₙᵢ dᵢ) exp(ikₙⱼ dⱼ) (distance offset is default 0 + // unless specified). + S_ij *= std::exp(1i * src_data.kn0 * src_data.d_offset); + S_ij *= std::exp(1i * data.kn0 * data.d_offset); + return S_ij; } std::complex PostOperator::GetPortPower(int idx) const { - ValidateDoPortMeasurement(); - // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx - auto it_lumped = measurment_cache.lumped_port_vi->find(idx); - if (it_lumped != measurment_cache.lumped_port_vi->end()) + // TODO: In multi-excitation PR we will guarantee that lumped & wave ports have unique idx + auto it_lumped = measurement_cache.lumped_port_vi.find(idx); + if (it_lumped != measurement_cache.lumped_port_vi.end()) { return it_lumped->second.P; } - auto it_wave = measurment_cache.wave_port_vi->find(idx); - if (it_wave != measurment_cache.wave_port_vi->end()) + auto it_wave = measurement_cache.wave_port_vi.find(idx); + if (it_wave != measurement_cache.wave_port_vi.end()) { return it_wave->second.P; } @@ -851,15 +684,14 @@ std::complex PostOperator::GetPortPower(int idx) const std::complex PostOperator::GetPortVoltage(int idx) const { - ValidateDoPortMeasurement(); - // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx - auto it_lumped = measurment_cache.lumped_port_vi->find(idx); - if (it_lumped != measurment_cache.lumped_port_vi->end()) + // TODO: In multi-excitation PR we will guarantee that lumped & wave ports have unique idx + auto it_lumped = measurement_cache.lumped_port_vi.find(idx); + if (it_lumped != measurement_cache.lumped_port_vi.end()) { return it_lumped->second.V; } - auto it_wave = measurment_cache.wave_port_vi->find(idx); - if (it_wave != measurment_cache.wave_port_vi->end()) + auto it_wave = measurement_cache.wave_port_vi.find(idx); + if (it_wave != measurement_cache.wave_port_vi.end()) { MFEM_ABORT("GetPortVoltage is not yet implemented for wave port boundaries!"); } @@ -870,10 +702,9 @@ std::complex PostOperator::GetPortVoltage(int idx) const std::complex PostOperator::GetPortCurrent(int idx, LumpedPortData::Branch branch) const { - ValidateDoPortMeasurement(); - // TODO: In multi-excittion PR we will gurantee that lumped & wave ports have unique idx - auto it_lumped = measurment_cache.lumped_port_vi->find(idx); - if (it_lumped != measurment_cache.lumped_port_vi->end()) + // TODO: In multi-excitation PR we will guarantee that lumped & wave ports have unique idx + auto it_lumped = measurement_cache.lumped_port_vi.find(idx); + if (it_lumped != measurement_cache.lumped_port_vi.end()) { auto &I_loc = it_lumped->second.I; switch (branch) @@ -888,8 +719,8 @@ std::complex PostOperator::GetPortCurrent(int idx, return std::accumulate(I_loc.begin(), I_loc.end(), std::complex{0.0, 0.0}); } } - auto it_wave = measurment_cache.wave_port_vi->find(idx); - if (it_wave != measurment_cache.wave_port_vi->end()) + auto it_wave = measurement_cache.wave_port_vi.find(idx); + if (it_wave != measurement_cache.wave_port_vi.end()) { MFEM_ABORT("GetPortCurrent is not yet implemented for wave port boundaries!"); } @@ -897,7 +728,8 @@ std::complex PostOperator::GetPortCurrent(int idx, "Port Current: Could not find a lumped or wave port with index {}!", idx)); } -double PostOperator::GetInductorParticipation(int idx, double E_m) const +double PostOperator::GetInductorParticipation(const LumpedPortOperator &lumped_port_op, + int idx, double E_m) const { // Compute energy-participation ratio of junction given by index idx for the field mode. // We first get the port line voltage, and use lumped port circuit impedance to get peak @@ -907,17 +739,14 @@ double PostOperator::GetInductorParticipation(int idx, double E_m) const // p_mj = 1/2 L_j I_mj² / E_m. // An element with no assigned inductance will be treated as having zero admittance and // thus zero current. - if (lumped_port_op == nullptr) - { - return 0.0; - } - const LumpedPortData &data = lumped_port_op->GetPort(idx); + const LumpedPortData &data = lumped_port_op.GetPort(idx); std::complex I_mj = GetPortCurrent(idx, LumpedPortData::Branch::L); return std::copysign(0.5 * std::abs(data.L) * std::real(I_mj * std::conj(I_mj)) / E_m, I_mj.real()); // mean(I²) = (I_r² + I_i²) / 2 } -double PostOperator::GetExternalKappa(int idx, double E_m) const +double PostOperator::GetExternalKappa(const LumpedPortOperator &lumped_port_op, int idx, + double E_m) const { // Compute participation ratio of external ports (given as any port boundary with // nonzero resistance). Currently no reactance of the ports is supported. The κ of the @@ -925,11 +754,7 @@ double PostOperator::GetExternalKappa(int idx, double E_m) const // κ_mj = 1/2 R_j I_mj² / E_m // from which the mode coupling quality factor is computed as: // Q_mj = ω_m / κ_mj. - if (lumped_port_op == nullptr) - { - return 0.0; - } - const LumpedPortData &data = lumped_port_op->GetPort(idx); + const LumpedPortData &data = lumped_port_op.GetPort(idx); std::complex I_mj = GetPortCurrent(idx, LumpedPortData::Branch::R); return std::copysign(0.5 * std::abs(data.R) * std::real(I_mj * std::conj(I_mj)) / E_m, I_mj.real()); // mean(I²) = (I_r² + I_i²) / 2 @@ -1083,37 +908,25 @@ void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const Mpi::Barrier(GetComm()); } -std::vector> PostOperator::ProbeEField() const +void PostOperator::MeasureProbes() { - if (!measurment_cache.probe_E_field.has_value()) + if (E && interp_op.GetProbes().size() > 0) { - MFEM_VERIFY(E, "PostOperator is not configured for electric field probes!"); - if (interp_op.GetProbes().size() > 0) - { - measurment_cache.probe_E_field = interp_op.ProbeField(*E); - } - else - { - measurment_cache.probe_E_field.emplace(); - } + measurement_cache.probe_E_field = interp_op.ProbeField(*E); } - return *measurment_cache.probe_E_field; + if (B && interp_op.GetProbes().size() > 0) + { + measurement_cache.probe_B_field = interp_op.ProbeField(*B); + } +} + +std::vector> PostOperator::ProbeEField() const +{ + return measurement_cache.probe_E_field; } std::vector> PostOperator::ProbeBField() const { - if (!measurment_cache.probe_B_field.has_value()) - { - MFEM_VERIFY(B, "PostOperator is not configured for magnetic flux density probes!"); - if (interp_op.GetProbes().size() > 0) - { - measurment_cache.probe_B_field = interp_op.ProbeField(*B); - } - else - { - measurment_cache.probe_B_field.emplace(); - } - } - return *measurment_cache.probe_B_field; + return measurement_cache.probe_B_field; } } // namespace palace diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index d750b30f4..15bfeab99 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -56,10 +56,6 @@ class PostOperator SurfacePostOperator surf_post_op; // Dielectric Interface Energy and Flux mutable InterpolationOperator interp_op; // E & B fields: mutates during measure - // Port Contributions: not owned, view onto space_op only, it must not go out of scope - LumpedPortOperator *lumped_port_op = nullptr; - WavePortOperator *wave_port_op = nullptr; - // Wave port boundary mode field postprocessing. struct WavePortFieldData { @@ -93,32 +89,43 @@ class PostOperator private: struct MeasurementCache { - std::optional> omega = std::nullopt; + std::complex omega = {0.0, 0.0}; - std::optional domain_E_field_energy_all = std::nullopt; - std::optional domain_H_field_energy_all = std::nullopt; + double domain_E_field_energy_all = 0.0; + double domain_H_field_energy_all = 0.0; - std::optional> domain_E_field_energy_i = std::nullopt; - std::optional> domain_H_field_energy_i = std::nullopt; + std::map domain_E_field_energy_i; + std::map domain_H_field_energy_i; - std::optional> surface_flux_i = std::nullopt; - std::optional> interface_eps_i = std::nullopt; + std::vector surface_flux_i; + std::vector interface_eps_i; - std::optional> lumped_port_vi = std::nullopt; - std::optional> wave_port_vi = std::nullopt; + std::map lumped_port_vi; + std::map wave_port_vi; - std::optional lumped_port_inductor_energy = std::nullopt; - std::optional lumped_port_capacitor_energy = std::nullopt; + double lumped_port_inductor_energy = 0.0; + double lumped_port_capacitor_energy = 0.0; - std::optional>> probe_E_field = std::nullopt; - std::optional>> probe_B_field = std::nullopt; + std::vector> probe_E_field; + std::vector> probe_B_field; }; - mutable MeasurementCache measurment_cache = {}; + MeasurementCache measurement_cache = {}; void ValidateDoPortMeasurement() const; void InitializeDataCollection(const IoData &iodata); + // Component measurements to fill the cache. + void MeasureEFieldEnergy(); + void MeasureHFieldEnergy(); + void MeasureSurfaceFlux(); + void MeasureProbes(); + void MeasureInterfaceEFieldEnergy(); + + // Measure and cache port voltages and currents for lumped and wave port operators. + void MeasureLumpedPorts(const LumpedPortOperator &lumped_port_op); + void MeasureWavePorts(const WavePortOperator &wave_port_op); + public: PostOperator(const IoData &iodata, SpaceOperator &space_op, const std::string &name); PostOperator(const IoData &iodata, LaplaceOperator &laplace_op, const std::string &name); @@ -168,12 +175,14 @@ class PostOperator } // Function that triggers all available post-processing measurements and populate cache. + // If SpaceOperator is provided, will perform port measurements. void MeasureAll(); + void MeasureAll(const SpaceOperator &space_op); // Clear internal measurement caches void ClearAllMeasurementCache(); - // Treat the frequency, for driven and eigemode solvers, as a "measurement", that other + // Treat the frequency, for driven and eigenmode solvers, as a "measurement", that other // measurements can depend on. This has to be supplied during the solver loop separate // from the fields. void SetFrequency(double omega); @@ -194,19 +203,21 @@ class PostOperator double GetHFieldEnergy(int idx) const; // Postprocess the electric or magnetic field flux for a surface index using the computed - // electcric field and/or magnetic flux density field solutions. - std::vector GetSurfaceFluxAll() const; + // electric field and/or magnetic flux density field solutions. + std::vector GetSurfaceFluxAll() const + { + return measurement_cache.surface_flux_i; + } FluxData GetSurfaceFlux(int idx) const; - // Postprocess the partitipation ratio for interface lossy dielectric losses in the + // Postprocess the participation ratio for interface lossy dielectric losses in the // electric field mode. double GetInterfaceParticipation(int idx, double E_m) const; - std::vector GetInterfaceEFieldEnergyAll() const; - InterfaceData GetInterfaceEFieldEnergy(int idx) const; - - // Measure and cache port voltages and currents for lumped and wave port operators. - void MeasureLumpedPorts() const; - void MeasureWavePorts() const; + const std::vector &GetInterfaceEFieldEnergyAll() const + { + return measurement_cache.interface_eps_i; + } + const InterfaceData &GetInterfaceEFieldEnergy(int idx) const; // Postprocess the energy in lumped capacitor or inductor port boundaries with index in // the provided set. @@ -215,7 +226,12 @@ class PostOperator // Postprocess the S-parameter for recieving lumped or wave port index using the electric // field solution. - std::complex GetSParameter(bool is_lumped_port, int idx, int source_idx) const; + // TODO: In multi-excitation PR we will guarantee that lumped & wave ports have unique idx + // TODO: Merge lumped and wave port S_ij calculations to allow both at same time. + std::complex GetSParameter(const LumpedPortOperator &lumped_port_op, int idx, + int source_idx) const; + std::complex GetSParameter(const WavePortOperator &wave_port_op, int idx, + int source_idx) const; // Postprocess the circuit voltage and current across lumped port index using the electric // field solution. When the internal grid functions are real-valued, the returned voltage @@ -227,10 +243,12 @@ class PostOperator LumpedPortData::Branch branch = LumpedPortData::Branch::TOTAL) const; // Postprocess the EPR for the electric field solution and lumped port index. - double GetInductorParticipation(int idx, double E_m) const; + double GetInductorParticipation(const LumpedPortOperator &lumped_port_op, int idx, + double E_m) const; // Postprocess the coupling rate for radiative loss to the given I-O port index. - double GetExternalKappa(int idx, double E_m) const; + double GetExternalKappa(const LumpedPortOperator &lumped_port_op, int idx, + double E_m) const; // Write to disk the E- and B-fields extracted from the solution vectors. Note that fields // are not redimensionalized, to do so one needs to compute: B <= B * (μ₀ H₀), E <= E * From febbbc34ce5221903ee24e73027fcda01b0c78a8 Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:51 -0800 Subject: [PATCH 18/21] Move simple accessors to header, gather measurements methods together in cpp --- palace/drivers/basesolver.cpp | 2 +- palace/models/postoperator.cpp | 134 ++++++++++++--------------------- palace/models/postoperator.hpp | 42 ++++++++--- 3 files changed, 80 insertions(+), 98 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index f43f467aa..d5a7e1144 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -451,7 +451,7 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimens using fmt::format; const bool has_imaginary = post_op.HasImag(); - auto flux_data_vec = post_op.GetSurfaceFluxAll(); + auto flux_data_vec = post_op.GetSurfaceFluxes(); auto dimensionlize_flux = [&iodata](auto Phi, SurfaceFluxType flux_type) { switch (flux_type) diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index b889adf6b..0c66abd85 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -370,36 +370,6 @@ void PostOperator::MeasureAll(const SpaceOperator &space_op) MeasureWavePorts(space_op.GetWavePortOp()); } -void PostOperator::SetFrequency(double omega) -{ - measurement_cache.omega = std::complex(omega); -} - -void PostOperator::SetFrequency(std::complex omega) -{ - measurement_cache.omega = omega; -} - -std::complex PostOperator::GetFrequency() const -{ - return measurement_cache.omega; -} - -double PostOperator::GetEFieldEnergy() const -{ - return measurement_cache.domain_E_field_energy_all; -} - -double PostOperator::GetEFieldEnergy(int idx) const -{ - auto it = measurement_cache.domain_E_field_energy_i.find(idx); - if (it == measurement_cache.domain_E_field_energy_i.end()) - { - MFEM_ABORT(fmt::format("Could not find domain index {} for E field energy!", idx)); - } - return it->second; -} - void PostOperator::MeasureEFieldEnergy() { measurement_cache.domain_E_field_energy_i.clear(); @@ -426,21 +396,6 @@ void PostOperator::MeasureHFieldEnergy() (!A && !B) ? 0.0 : dom_post_op.GetMagneticFieldEnergy(A ? *A : *B); } -double PostOperator::GetHFieldEnergy() const -{ - return measurement_cache.domain_H_field_energy_all; -} - -double PostOperator::GetHFieldEnergy(int idx) const -{ - auto it = measurement_cache.domain_H_field_energy_i.find(idx); - if (it == measurement_cache.domain_H_field_energy_i.end()) - { - MFEM_ABORT(fmt::format("Could not find domain index {} for H field energy!", idx)); - } - return it->second; -} - void PostOperator::MeasureSurfaceFlux() { // Compute the flux through a surface as Φ_j = ∫ F ⋅ n_j dS, with F = B, F = ε D, or F = @@ -459,18 +414,6 @@ void PostOperator::MeasureSurfaceFlux() } } -PostOperator::FluxData PostOperator::GetSurfaceFlux(int idx) const -{ - auto it = std::find_if(measurement_cache.surface_flux_i.begin(), - measurement_cache.surface_flux_i.end(), - [idx](const auto &d) { return d.idx == idx; }); - if (it == measurement_cache.surface_flux_i.end()) - { - MFEM_ABORT(fmt::format("Could not find surface index {} for flux!", idx)); - } - return *it; -} - void PostOperator::MeasureInterfaceEFieldEnergy() { // Compute the surface dielectric participation ratio and associated quality factor for @@ -492,30 +435,6 @@ void PostOperator::MeasureInterfaceEFieldEnergy() } } -const PostOperator::InterfaceData &PostOperator::GetInterfaceEFieldEnergy(int idx) const -{ - auto it = std::find_if(measurement_cache.interface_eps_i.begin(), - measurement_cache.interface_eps_i.end(), - [idx](const auto &d) { return d.idx == idx; }); - if (it == measurement_cache.interface_eps_i.end()) - { - MFEM_ABORT(fmt::format("Could not find surface index {} for interface energy!", idx)); - } - return *it; -} - -double PostOperator::GetInterfaceParticipation(int idx, double E_m) const -{ - // Compute the surface dielectric participation ratio and associated quality factor for - // the material interface given by index idx. We have: - // 1/Q_mj = p_mj tan(δ)_j - // with: - // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). - MFEM_VERIFY(E, "Surface Q not defined, no electric field solution found!"); - auto data = GetInterfaceEFieldEnergy(idx); - return data.energy / E_m; -} - void PostOperator::MeasureLumpedPorts(const LumpedPortOperator &lumped_port_op) { measurement_cache.lumped_port_vi.clear(); @@ -607,15 +526,60 @@ void PostOperator::MeasureWavePorts(const WavePortOperator &wave_port_op) // (Z = V² / P, I = V / Z) } } +double PostOperator::GetEFieldEnergy(int idx) const +{ + auto it = measurement_cache.domain_E_field_energy_i.find(idx); + if (it == measurement_cache.domain_E_field_energy_i.end()) + { + MFEM_ABORT(fmt::format("Could not find domain index {} for E field energy!", idx)); + } + return it->second; +} + +double PostOperator::GetHFieldEnergy(int idx) const +{ + auto it = measurement_cache.domain_H_field_energy_i.find(idx); + if (it == measurement_cache.domain_H_field_energy_i.end()) + { + MFEM_ABORT(fmt::format("Could not find domain index {} for H field energy!", idx)); + } + return it->second; +} + +PostOperator::FluxData PostOperator::GetSurfaceFlux(int idx) const +{ + auto it = std::find_if(measurement_cache.surface_flux_i.begin(), + measurement_cache.surface_flux_i.end(), + [idx](const auto &d) { return d.idx == idx; }); + if (it == measurement_cache.surface_flux_i.end()) + { + MFEM_ABORT(fmt::format("Could not find surface index {} for flux!", idx)); + } + return *it; +} -double PostOperator::GetLumpedInductorEnergy() const +const PostOperator::InterfaceData &PostOperator::GetInterfaceEFieldEnergy(int idx) const { - return measurement_cache.lumped_port_inductor_energy; + auto it = std::find_if(measurement_cache.interface_eps_i.begin(), + measurement_cache.interface_eps_i.end(), + [idx](const auto &d) { return d.idx == idx; }); + if (it == measurement_cache.interface_eps_i.end()) + { + MFEM_ABORT(fmt::format("Could not find surface index {} for interface energy!", idx)); + } + return *it; } -double PostOperator::GetLumpedCapacitorEnergy() const +double PostOperator::GetInterfaceParticipation(int idx, double E_m) const { - return measurement_cache.lumped_port_capacitor_energy; + // Compute the surface dielectric participation ratio and associated quality factor for + // the material interface given by index idx. We have: + // 1/Q_mj = p_mj tan(δ)_j + // with: + // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). + MFEM_VERIFY(E, "Surface Q not defined, no electric field solution found!"); + auto data = GetInterfaceEFieldEnergy(idx); + return data.energy / E_m; } std::complex PostOperator::GetSParameter(const LumpedPortOperator &lumped_port_op, diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index 15bfeab99..dde5e9ec2 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -111,8 +111,6 @@ class PostOperator }; MeasurementCache measurement_cache = {}; - void ValidateDoPortMeasurement() const; - void InitializeDataCollection(const IoData &iodata); // Component measurements to fill the cache. @@ -185,17 +183,31 @@ class PostOperator // Treat the frequency, for driven and eigenmode solvers, as a "measurement", that other // measurements can depend on. This has to be supplied during the solver loop separate // from the fields. - void SetFrequency(double omega); - void SetFrequency(std::complex omega); + void SetFrequency(double omega) + { + measurement_cache.omega = std::complex(omega); + } + void SetFrequency(std::complex omega) + { + measurement_cache.omega = omega; + } - // Return stored frequency that was given in SetFrequency. Always promotes to complex - // frequency. - std::complex GetFrequency() const; + // Return stored frequency that was given in SetFrequency. + std::complex GetFrequency() const + { + return measurement_cache.omega; + } // Postprocess the total electric and magnetic field energies in the electric and magnetic // fields. - double GetEFieldEnergy() const; - double GetHFieldEnergy() const; + double GetEFieldEnergy() const + { + return measurement_cache.domain_E_field_energy_all; + } + double GetHFieldEnergy() const + { + return measurement_cache.domain_H_field_energy_all; + } // Postprocess the electric and magnetic field energies in the domain with the given // index. @@ -204,7 +216,7 @@ class PostOperator // Postprocess the electric or magnetic field flux for a surface index using the computed // electric field and/or magnetic flux density field solutions. - std::vector GetSurfaceFluxAll() const + std::vector GetSurfaceFluxes() const { return measurement_cache.surface_flux_i; } @@ -221,8 +233,14 @@ class PostOperator // Postprocess the energy in lumped capacitor or inductor port boundaries with index in // the provided set. - double GetLumpedInductorEnergy() const; - double GetLumpedCapacitorEnergy() const; + double GetLumpedInductorEnergy() const + { + return measurement_cache.lumped_port_inductor_energy; + } + double GetLumpedCapacitorEnergy() const + { + return measurement_cache.lumped_port_capacitor_energy; + } // Postprocess the S-parameter for recieving lumped or wave port index using the electric // field solution. From f46d7f6bfcc7369060029b3b807743d3e24e5a49 Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:51 -0800 Subject: [PATCH 19/21] Trying to fix issue with cmake cache --- cmake/ExternalMFEM.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ExternalMFEM.cmake b/cmake/ExternalMFEM.cmake index 1de07a565..5c82d4566 100644 --- a/cmake/ExternalMFEM.cmake +++ b/cmake/ExternalMFEM.cmake @@ -77,7 +77,7 @@ if(CMAKE_BUILD_TYPE MATCHES "Debug|debug|DEBUG") endif() # Replace mfem abort calls with exceptions for testing, default off -set(PALACE_MFEM_USE_EXCEPTIONS CACHE NO "MFEM throw exceptsions instead of abort calls") +set(PALACE_MFEM_USE_EXCEPTIONS NO "MFEM throw exceptions instead of abort calls") set(MFEM_OPTIONS ${PALACE_SUPERBUILD_DEFAULT_ARGS}) list(APPEND MFEM_OPTIONS From ca06015d12bb96df9ccfad158d0455881c56cd36 Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:51 -0800 Subject: [PATCH 20/21] Initial upgrade to remove state from printers, only driven should be complete, does not compile --- palace/drivers/basesolver.cpp | 92 +++++++++--------------------- palace/drivers/basesolver.hpp | 50 +++++------------ palace/drivers/drivensolver.cpp | 99 +++++++++++---------------------- palace/drivers/drivensolver.hpp | 19 ++----- 4 files changed, 75 insertions(+), 185 deletions(-) diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index d5a7e1144..ff499eb1e 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -286,14 +286,12 @@ void BaseSolver::SaveMetadata(const Timer &timer) const } } -BaseSolver::DomainsPostPrinter::DomainsPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, +BaseSolver::DomainsPostPrinter::DomainsPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) - : do_measurement_{do_measurement}, root_{root} { - if (!do_measurement_ || !root_) + if (!Mpi::Root(post_op.GetComm())) { return; } @@ -322,10 +320,6 @@ void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionfu const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; @@ -356,26 +350,14 @@ void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionfu domain_E.WriteFullTableTrunc(); } -BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, +BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) - : root_{root}, - do_measurement_flux_(do_measurement // - && post_op.GetSurfacePostOp().flux_surfs.size() > 0 // Has flux - ), - do_measurement_eps_(do_measurement // - && post_op.GetSurfacePostOp().eps_surfs.size() > 0 // Has eps - ) { - if (!root_) - { - return; - } + if (!Mpi::Root(post_op.GetComm())) { return; } using fmt::format; - - if (do_measurement_flux_) + if (post_op.GetSurfacePostOp().flux_surfs.size() > 0) { surface_F = TableWithCSVFile(post_dir / "surface-F.csv"); surface_F.table.reserve(n_expected_rows, @@ -424,7 +406,7 @@ BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(bool do_measurement, bool r surface_F.AppendHeader(); } - if (do_measurement_eps_) + if (post_op.GetSurfacePostOp().eps_surfs.size() > 0) { surface_Q = TableWithCSVFile(post_dir / "surface-Q.csv"); surface_Q.table.reserve(n_expected_rows, @@ -443,10 +425,6 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementFlux(double idx_value_dimens const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_flux_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; @@ -488,10 +466,6 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurementEps(double idx_value_dimensi const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_eps_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; @@ -519,32 +493,29 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurement(double idx_value_dimensionf // If surfaces have been specified for postprocessing, compute the corresponding values // and write out to disk. The passed in E_elec is the sum of the E-field and lumped // capacitor energies, and E_mag is the same for the B-field and lumped inductors. - AddMeasurementFlux(idx_value_dimensionful, post_op, iodata); - AddMeasurementEps(idx_value_dimensionful, post_op, iodata); + if (post_op.GetSurfacePostOp().flux_surfs.size() > 0) + { + AddMeasurementFlux(idx_value_dimensionful, post_op, iodata); + } + if (post_op.GetSurfacePostOp().eps_surfs.size() > 0) + { + AddMeasurementEps(idx_value_dimensionful, post_op, iodata); + } } -BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, +BaseSolver::ProbePostPrinter::ProbePostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows) - : root_{root}, do_measurement_E_{do_measurement}, do_measurement_B_{do_measurement}, - has_imag{post_op.HasImag()}, v_dim{post_op.GetInterpolationOpVDim()} { #if defined(MFEM_USE_GSLIB) - do_measurement_E_ = do_measurement_E_ // - && (post_op.GetProbes().size() > 0) // Has probes defined - && post_op.HasE(); // Has E fields - - do_measurement_B_ = do_measurement_B_ // - && (post_op.GetProbes().size() > 0) // Has probes defined - && post_op.HasB(); // Has B fields - - if (!root_ || (!do_measurement_E_ && !do_measurement_B_)) + if (!Mpi::Root(post_op.GetComm()) || post_op.GetProbes().size() == 0) { return; } using fmt::format; + const int v_dim = post_op.GetInterpolationOpVDim(); + const bool has_imag = post_op.HasImag(); int scale_col = (has_imag ? 2 : 1) * v_dim; auto dim_labeler = [](int i) -> std::string { @@ -562,7 +533,7 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, } }; - if (do_measurement_E_) + if (post_op.HasE()) { probe_E = TableWithCSVFile(post_dir / "probe-E.csv"); probe_E.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); @@ -591,7 +562,7 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(bool do_measurement, bool root, probe_E.AppendHeader(); } - if (do_measurement_B_) + if (post_op.HasB()) { probe_B = TableWithCSVFile(post_dir / "probe-B.csv"); probe_B.table.reserve(n_expected_rows, scale_col * post_op.GetProbes().size()); @@ -626,14 +597,12 @@ void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_E_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; auto probe_field = post_op.ProbeEField(); + const int v_dim = post_op.GetInterpolationOpVDim(); + const bool has_imag = post_op.HasImag(); MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), @@ -661,14 +630,12 @@ void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { - if (!do_measurement_B_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; auto probe_field = post_op.ProbeBField(); + const int v_dim = post_op.GetInterpolationOpVDim(); + const bool has_imag = post_op.HasImag(); MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), @@ -702,15 +669,8 @@ void BaseSolver::ProbePostPrinter::AddMeasurement(double idx_value_dimensionful, #endif } -BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(bool do_measurement, - bool root, - const fs::path &post_dir) - : root_{root}, do_measurement_{do_measurement} +BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(const fs::path &post_dir) { - if (!do_measurement_ || !root_) - { - return; - } error_indicator = TableWithCSVFile(post_dir / "error-indicators.csv"); error_indicator.table.reserve(1, 4); @@ -723,7 +683,7 @@ BaseSolver::ErrorIndicatorPostPrinter::ErrorIndicatorPostPrinter(bool do_measure void BaseSolver::ErrorIndicatorPostPrinter::PrintIndicatorStatistics( const PostOperator &post_op, const ErrorIndicator::SummaryStatistics &indicator_stats) { - if (!do_measurement_ || !root_) + if (!Mpi::Root(post_op.GetComm())) { return; } diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index 370f33b02..4408bf9e6 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -33,70 +33,47 @@ class BaseSolver // Parameters for writing postprocessing outputs. fs::path post_dir; - bool root; // Common domain postprocessing for all simulation types. class DomainsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile domain_E; - public: DomainsPostPrinter() = default; - DomainsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const PostOperator &post_op, const std::string &idx_col_name, - int n_expected_rows); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - const IoData &iodata); + DomainsPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); }; // Common surface postprocessing for all simulation types. class SurfacesPostPrinter { - bool root_ = false; - bool do_measurement_flux_ = false; - bool do_measurement_eps_ = false; TableWithCSVFile surface_F; TableWithCSVFile surface_Q; - - public: - SurfacesPostPrinter() = default; - SurfacesPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const PostOperator &post_op, const std::string &idx_col_name, - int n_expected_rows); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - const IoData &iodata); void AddMeasurementFlux(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); void AddMeasurementEps(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + public: + SurfacesPostPrinter() = default; + SurfacesPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); }; // Common probe postprocessing for all simulation types. class ProbePostPrinter { - bool root_ = false; - bool do_measurement_E_ = false; - bool do_measurement_B_ = false; TableWithCSVFile probe_E; TableWithCSVFile probe_B; - int v_dim = 0; - bool has_imag = false; - - public: - ProbePostPrinter() = default; - ProbePostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const PostOperator &post_op, const std::string &idx_col_name, - int n_expected_rows); - void AddMeasurementE(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); void AddMeasurementB(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, - const IoData &iodata); + public: + ProbePostPrinter() = default; + ProbePostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); + + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); }; // Common error indicator postprocessing for all simulation types. // @@ -110,10 +87,9 @@ class BaseSolver public: ErrorIndicatorPostPrinter() = default; - ErrorIndicatorPostPrinter(bool do_measurement, bool root, const fs::path &post_dir); + ErrorIndicatorPostPrinter(const fs::path &post_dir); - void PrintIndicatorStatistics(const PostOperator &post_op, - const ErrorIndicator::SummaryStatistics &indicator_stats); + void PrintIndicatorStatistics(const PostOperator &post_op, const ErrorIndicator::SummaryStatistics &indicator_stats); }; // Performs a solve using the mesh sequence, then reports error indicators and the number diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index 3725b5c7f..89b0c012b 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -405,14 +405,10 @@ int DrivenSolver::GetNumSteps(double start, double end, double delta) const // Measurements / Postprocessing DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( - bool do_measurement, bool root, const fs::path &post_dir, - const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) - : root_{root}, do_measurement_{ - do_measurement // - && (surf_j_op.Size() > 0) // Needs surface currents - } + const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows) { - if (!do_measurement_ || !root_) + const auto &surf_j_op = space_op.GetSurfaceCurrentOp(); + if (!Mpi::Root(space_op.GetComm()) || surf_j_op.Size() == 0) { return; } @@ -430,10 +426,6 @@ DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( void DrivenSolver::CurrentsPostPrinter::AddMeasurement( double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) { - if (!do_measurement_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; @@ -446,16 +438,12 @@ void DrivenSolver::CurrentsPostPrinter::AddMeasurement( surface_I.AppendRow(); } -DrivenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, +DrivenSolver::PortsPostPrinter::PortsPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, int n_expected_rows) - : root_{root}, do_measurement_{ - do_measurement // - && (lumped_port_op.Size() > 0) // Only works for lumped ports - } { - if (!do_measurement_ || !root_) + const auto &lumped_port_op = space_op.GetLumpedPortOp(); + if (!Mpi::Root(space_op.GetComm()) || lumped_port_op.Size() == 0) { return; } @@ -490,10 +478,6 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_ || !root_) - { - return; - } using VT = IoData::ValueType; // Postprocess the frequency domain lumped port voltages and currents (complex magnitude @@ -528,25 +512,13 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( port_I.AppendRow(); } -DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( - bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, - int n_expected_rows) - : root_{root}, - do_measurement_{ - do_measurement // - && ((lumped_port_op.Size() > 0) xor - (wave_port_op.Size() > 0)) // either lumped or wave but not both - - }, - src_lumped_port{lumped_port_op.Size() > 0} +DrivenSolver::SParametersPostPrinter::SParametersPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows) { - if (!do_measurement_ || !root_) - { - return; - } + const auto &lumped_port_op = space_op.GetLumpedPortOp(); + const auto &wave_port_op = space_op.GetWavePortOp(); + // Get excitation index as is currently done: if -1 then no excitation - // Already ensured that one of lumped or wave ports are empty + // Already ensured that one of lumped or wave ports are non-empty for (const auto &[idx, data] : lumped_port_op) { if (data.excitation) @@ -562,12 +534,12 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter( } } - do_measurement_ = do_measurement_ && (source_idx > 0); - - if (!do_measurement_ || !root_) + const bool do_measurement = ((lumped_port_op.Size() > 0) xor (wave_port_op.Size() > 0)) && (source_idx > 0); + if (!Mpi::Root(space_op.GetComm()) || !do_measurement) { return; } + using fmt::format; port_S = TableWithCSVFile(post_dir / "port-S.csv"); port_S.table.reserve(n_expected_rows, lumped_port_op.Size()); @@ -595,10 +567,6 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata) { - if (!do_measurement_ || !root_) - { - return; - } using VT = IoData::ValueType; using fmt::format; @@ -639,18 +607,13 @@ DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( bool root, const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_expected_rows, int delta_post_) : delta_post{delta_post_}, write_paraview_fields{delta_post_ > 0}, - domains{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, - surfaces{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, - currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, - probes{true, root, post_dir, post_op, "f (GHz)", n_expected_rows}, - ports{true, root, post_dir, space_op.GetLumpedPortOp(), n_expected_rows}, - s_parameters{true, - root, - post_dir, - space_op.GetLumpedPortOp(), - space_op.GetWavePortOp(), - n_expected_rows}, - error_indicator{true, root, post_dir} + domains{post_dir, post_op, "f (GHz)", n_expected_rows}, + surfaces{post_dir, post_op, "f (GHz)", n_expected_rows}, + currents{post_dir, space_op, n_expected_rows}, + probes{post_dir, post_op, "f (GHz)", n_expected_rows}, + ports{post_dir, space_op, n_expected_rows}, + s_parameters{post_dir, space_op, n_expected_rows}, + error_indicator{post_dir} { } @@ -661,14 +624,16 @@ void DrivenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata { double omega = post_op.GetFrequency().real(); auto freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); - - domains.AddMeasurement(freq, post_op, iodata); - surfaces.AddMeasurement(freq, post_op, iodata); - currents.AddMeasurement(freq, space_op.GetSurfaceCurrentOp(), iodata); - probes.AddMeasurement(freq, post_op, iodata); - ports.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), iodata); - s_parameters.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), - space_op.GetWavePortOp(), iodata); + if (Mpi::Root(post_op.GetComm())) + { + domains.AddMeasurement(freq, post_op, iodata); + surfaces.AddMeasurement(freq, post_op, iodata); + currents.AddMeasurement(freq, space_op.GetSurfaceCurrentOp(), iodata); + probes.AddMeasurement(freq, post_op, iodata); + ports.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), iodata); + s_parameters.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), + space_op.GetWavePortOp(), iodata); + } // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && (step % delta_post == 0)) { diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index d51bc1230..77df4f75f 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -32,29 +32,20 @@ class DrivenSolver : public BaseSolver class CurrentsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile surface_I; - public: CurrentsPostPrinter() = default; - CurrentsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); - void AddMeasurement(double freq, const SurfaceCurrentOperator &surf_j_op, - const IoData &iodata); + CurrentsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); + void AddMeasurement(double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata); }; class PortsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile port_V; TableWithCSVFile port_I; - public: PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, int n_expected_rows); + PortsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; @@ -76,9 +67,7 @@ class DrivenSolver : public BaseSolver public: SParametersPostPrinter() = default; - SParametersPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, - const WavePortOperator &wave_port_op, int n_expected_rows); + SParametersPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata); From 4e91bcf019ee23337f4912c1cc88877e0150ca5d Mon Sep 17 00:00:00 2001 From: Hugh Carson Date: Tue, 21 Jan 2025 15:50:52 -0800 Subject: [PATCH 21/21] Remove state from more printers. --- cmake/ExternalMFEM.cmake | 2 +- palace/drivers/basesolver.cpp | 31 +++++++++-- palace/drivers/basesolver.hpp | 31 ++++++----- palace/drivers/drivensolver.cpp | 65 +++++++++++++--------- palace/drivers/drivensolver.hpp | 19 ++++--- palace/drivers/eigensolver.cpp | 74 ++++++++++---------------- palace/drivers/eigensolver.hpp | 30 ++++------- palace/drivers/electrostaticsolver.cpp | 11 ++-- palace/drivers/electrostaticsolver.hpp | 4 +- palace/drivers/magnetostaticsolver.cpp | 11 ++-- palace/drivers/magnetostaticsolver.hpp | 4 +- palace/drivers/transientsolver.cpp | 60 +++++++++------------ palace/drivers/transientsolver.hpp | 22 +++----- palace/models/postoperator.cpp | 9 ---- palace/models/postoperator.hpp | 31 +++-------- palace/utils/tablecsv.hpp | 2 +- 16 files changed, 189 insertions(+), 217 deletions(-) diff --git a/cmake/ExternalMFEM.cmake b/cmake/ExternalMFEM.cmake index 5c82d4566..58fa5fd58 100644 --- a/cmake/ExternalMFEM.cmake +++ b/cmake/ExternalMFEM.cmake @@ -77,7 +77,7 @@ if(CMAKE_BUILD_TYPE MATCHES "Debug|debug|DEBUG") endif() # Replace mfem abort calls with exceptions for testing, default off -set(PALACE_MFEM_USE_EXCEPTIONS NO "MFEM throw exceptions instead of abort calls") +set(PALACE_MFEM_USE_EXCEPTIONS NO) set(MFEM_OPTIONS ${PALACE_SUPERBUILD_DEFAULT_ARGS}) list(APPEND MFEM_OPTIONS diff --git a/palace/drivers/basesolver.cpp b/palace/drivers/basesolver.cpp index ff499eb1e..c397b1fdc 100644 --- a/palace/drivers/basesolver.cpp +++ b/palace/drivers/basesolver.cpp @@ -320,6 +320,10 @@ void BaseSolver::DomainsPostPrinter::AddMeasurement(double idx_value_dimensionfu const PostOperator &post_op, const IoData &iodata) { + if (!Mpi::Root(post_op.GetComm())) + { + return; + } using VT = IoData::ValueType; using fmt::format; @@ -355,7 +359,10 @@ BaseSolver::SurfacesPostPrinter::SurfacesPostPrinter(const fs::path &post_dir, const std::string &idx_col_name, int n_expected_rows) { - if (!Mpi::Root(post_op.GetComm())) { return; } + if (!Mpi::Root(post_op.GetComm())) + { + return; + } using fmt::format; if (post_op.GetSurfacePostOp().flux_surfs.size() > 0) { @@ -490,6 +497,10 @@ void BaseSolver::SurfacesPostPrinter::AddMeasurement(double idx_value_dimensionf const PostOperator &post_op, const IoData &iodata) { + if (!Mpi::Root(post_op.GetComm())) + { + return; + } // If surfaces have been specified for postprocessing, compute the corresponding values // and write out to disk. The passed in E_elec is the sum of the E-field and lumped // capacitor energies, and E_mag is the same for the B-field and lumped inductors. @@ -509,7 +520,7 @@ BaseSolver::ProbePostPrinter::ProbePostPrinter(const fs::path &post_dir, int n_expected_rows) { #if defined(MFEM_USE_GSLIB) - if (!Mpi::Root(post_op.GetComm()) || post_op.GetProbes().size() == 0) + if (post_op.GetProbes().size() == 0 || !Mpi::Root(post_op.GetComm())) { return; } @@ -597,6 +608,10 @@ void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { + if (!post_op.HasE()) + { + return; + } using VT = IoData::ValueType; using fmt::format; @@ -604,7 +619,7 @@ void BaseSolver::ProbePostPrinter::AddMeasurementE(double idx_value_dimensionful const int v_dim = post_op.GetInterpolationOpVDim(); const bool has_imag = post_op.HasImag(); MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), - format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", + format("Size mismatch: expect vector field to have size {} * {} = {}; got {}", v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), probe_field.size())) @@ -630,6 +645,10 @@ void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful const PostOperator &post_op, const IoData &iodata) { + if (!post_op.HasB()) + { + return; + } using VT = IoData::ValueType; using fmt::format; @@ -637,7 +656,7 @@ void BaseSolver::ProbePostPrinter::AddMeasurementB(double idx_value_dimensionful const int v_dim = post_op.GetInterpolationOpVDim(); const bool has_imag = post_op.HasImag(); MFEM_VERIFY(probe_field.size() == v_dim * post_op.GetProbes().size(), - format("Size mismatch: expect vector field to ahve size {} * {} = {}; got {}", + format("Size mismatch: expect vector field to have size {} * {} = {}; got {}", v_dim, post_op.GetProbes().size(), v_dim * post_op.GetProbes().size(), probe_field.size())) @@ -664,6 +683,10 @@ void BaseSolver::ProbePostPrinter::AddMeasurement(double idx_value_dimensionful, const IoData &iodata) { #if defined(MFEM_USE_GSLIB) + if (!Mpi::Root(post_op.GetComm()) || post_op.GetProbes().size() == 0) + { + return; + } AddMeasurementE(idx_value_dimensionful, post_op, iodata); AddMeasurementB(idx_value_dimensionful, post_op, iodata); #endif diff --git a/palace/drivers/basesolver.hpp b/palace/drivers/basesolver.hpp index 4408bf9e6..31f7bc6d1 100644 --- a/palace/drivers/basesolver.hpp +++ b/palace/drivers/basesolver.hpp @@ -33,15 +33,18 @@ class BaseSolver // Parameters for writing postprocessing outputs. fs::path post_dir; + bool root; // Common domain postprocessing for all simulation types. class DomainsPostPrinter { TableWithCSVFile domain_E; + public: - DomainsPostPrinter() = default; - DomainsPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + DomainsPostPrinter(const fs::path &post_dir, const PostOperator &post_op, + const std::string &idx_col_name, int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); }; // Common surface postprocessing for all simulation types. @@ -53,10 +56,12 @@ class BaseSolver const IoData &iodata); void AddMeasurementEps(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + public: - SurfacesPostPrinter() = default; - SurfacesPostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + SurfacesPostPrinter(const fs::path &post_dir, const PostOperator &post_op, + const std::string &idx_col_name, int n_expected_rows); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); }; // Common probe postprocessing for all simulation types. @@ -69,11 +74,13 @@ class BaseSolver const IoData &iodata); void AddMeasurementB(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + public: - ProbePostPrinter() = default; - ProbePostPrinter(const fs::path &post_dir, const PostOperator &post_op, const std::string &idx_col_name, int n_expected_rows); + ProbePostPrinter(const fs::path &post_dir, const PostOperator &post_op, + const std::string &idx_col_name, int n_expected_rows); - void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, const IoData &iodata); + void AddMeasurement(double idx_value_dimensionful, const PostOperator &post_op, + const IoData &iodata); }; // Common error indicator postprocessing for all simulation types. // @@ -81,15 +88,13 @@ class BaseSolver // step (time / frequency / eigenvector). class ErrorIndicatorPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile error_indicator; public: - ErrorIndicatorPostPrinter() = default; ErrorIndicatorPostPrinter(const fs::path &post_dir); - void PrintIndicatorStatistics(const PostOperator &post_op, const ErrorIndicator::SummaryStatistics &indicator_stats); + void PrintIndicatorStatistics(const PostOperator &post_op, + const ErrorIndicator::SummaryStatistics &indicator_stats); }; // Performs a solve using the mesh sequence, then reports error indicators and the number diff --git a/palace/drivers/drivensolver.cpp b/palace/drivers/drivensolver.cpp index 89b0c012b..eaf568306 100644 --- a/palace/drivers/drivensolver.cpp +++ b/palace/drivers/drivensolver.cpp @@ -401,14 +401,12 @@ int DrivenSolver::GetNumSteps(double start, double end, double delta) const (delta > 0.0 && dfinal - end < delta_eps * end)); } -// ----------------- -// Measurements / Postprocessing - -DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( - const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows) +DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, + int n_expected_rows) { const auto &surf_j_op = space_op.GetSurfaceCurrentOp(); - if (!Mpi::Root(space_op.GetComm()) || surf_j_op.Size() == 0) + if (surf_j_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -423,9 +421,15 @@ DrivenSolver::CurrentsPostPrinter::CurrentsPostPrinter( surface_I.AppendHeader(); } -void DrivenSolver::CurrentsPostPrinter::AddMeasurement( - double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) +void DrivenSolver::CurrentsPostPrinter::AddMeasurement(double freq, + const SpaceOperator &space_op, + const IoData &iodata) { + const auto &surf_j_op = space_op.GetSurfaceCurrentOp(); + if (surf_j_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) + { + return; + } using VT = IoData::ValueType; using fmt::format; @@ -443,7 +447,7 @@ DrivenSolver::PortsPostPrinter::PortsPostPrinter(const fs::path &post_dir, int n_expected_rows) { const auto &lumped_port_op = space_op.GetLumpedPortOp(); - if (!Mpi::Root(space_op.GetComm()) || lumped_port_op.Size() == 0) + if (lumped_port_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -478,6 +482,10 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { + if (lumped_port_op.Size() == 0 || !Mpi::Root(post_op.GetComm())) + { + return; + } using VT = IoData::ValueType; // Postprocess the frequency domain lumped port voltages and currents (complex magnitude @@ -512,7 +520,9 @@ void DrivenSolver::PortsPostPrinter::AddMeasurement( port_I.AppendRow(); } -DrivenSolver::SParametersPostPrinter::SParametersPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows) +DrivenSolver::SParametersPostPrinter::SParametersPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, + int n_expected_rows) { const auto &lumped_port_op = space_op.GetLumpedPortOp(); const auto &wave_port_op = space_op.GetWavePortOp(); @@ -534,8 +544,9 @@ DrivenSolver::SParametersPostPrinter::SParametersPostPrinter(const fs::path &pos } } - const bool do_measurement = ((lumped_port_op.Size() > 0) xor (wave_port_op.Size() > 0)) && (source_idx > 0); - if (!Mpi::Root(space_op.GetComm()) || !do_measurement) + const bool do_measurement = + ((lumped_port_op.Size() > 0) xor (wave_port_op.Size() > 0)) && (source_idx > 0); + if (!do_measurement || !Mpi::Root(space_op.GetComm())) { return; } @@ -567,6 +578,10 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata) { + if (source_idx == -1 || !Mpi::Root(post_op.GetComm())) + { + return; + } using VT = IoData::ValueType; using fmt::format; @@ -586,8 +601,9 @@ void DrivenSolver::SParametersPostPrinter::AddMeasurement( for (const auto o_idx : all_port_indices) { std::complex S_ij = - src_lumped_port ? post_op.GetSParameter(lumped_port_op, o_idx, source_idx) - : post_op.GetSParameter(wave_port_op, o_idx, source_idx); + (lumped_port_op.Size() > 0) + ? post_op.GetSParameter(lumped_port_op, o_idx, source_idx) + : post_op.GetSParameter(wave_port_op, o_idx, source_idx); auto abs_S_ij = 20.0 * std::log10(std::abs(S_ij)); auto arg_S_ij = std::arg(S_ij) * 180.0 / M_PI; @@ -612,8 +628,7 @@ DrivenSolver::PostprocessPrintResults::PostprocessPrintResults( currents{post_dir, space_op, n_expected_rows}, probes{post_dir, post_op, "f (GHz)", n_expected_rows}, ports{post_dir, space_op, n_expected_rows}, - s_parameters{post_dir, space_op, n_expected_rows}, - error_indicator{post_dir} + s_parameters{post_dir, space_op, n_expected_rows}, error_indicator{post_dir} { } @@ -624,16 +639,14 @@ void DrivenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata { double omega = post_op.GetFrequency().real(); auto freq = iodata.DimensionalizeValue(IoData::ValueType::FREQUENCY, omega); - if (Mpi::Root(post_op.GetComm())) - { - domains.AddMeasurement(freq, post_op, iodata); - surfaces.AddMeasurement(freq, post_op, iodata); - currents.AddMeasurement(freq, space_op.GetSurfaceCurrentOp(), iodata); - probes.AddMeasurement(freq, post_op, iodata); - ports.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), iodata); - s_parameters.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), - space_op.GetWavePortOp(), iodata); - } + domains.AddMeasurement(freq, post_op, iodata); + surfaces.AddMeasurement(freq, post_op, iodata); + currents.AddMeasurement(freq, space_op, iodata); + probes.AddMeasurement(freq, post_op, iodata); + ports.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), iodata); + s_parameters.AddMeasurement(freq, post_op, space_op.GetLumpedPortOp(), + space_op.GetWavePortOp(), iodata); + // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && (step % delta_post == 0)) { diff --git a/palace/drivers/drivensolver.hpp b/palace/drivers/drivensolver.hpp index 77df4f75f..f11e10823 100644 --- a/palace/drivers/drivensolver.hpp +++ b/palace/drivers/drivensolver.hpp @@ -33,19 +33,21 @@ class DrivenSolver : public BaseSolver class CurrentsPostPrinter { TableWithCSVFile surface_I; + public: - CurrentsPostPrinter() = default; - CurrentsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); - void AddMeasurement(double freq, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata); + CurrentsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); + void AddMeasurement(double freq, const SpaceOperator &space_op, const IoData &iodata); }; class PortsPostPrinter { TableWithCSVFile port_V; TableWithCSVFile port_I; + public: - PortsPostPrinter() = default; - PortsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); + PortsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; @@ -56,9 +58,6 @@ class DrivenSolver : public BaseSolver // excited port index specified in the configuration file, storing |S_ij| and arg // (S_ij) in dB and degrees, respectively. S-parameter output is only available for a // single lumped or wave port excitation. - - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile port_S; // Currently can't mix lumped and surface ports for s-matrix @@ -66,8 +65,8 @@ class DrivenSolver : public BaseSolver int source_idx = -1; public: - SParametersPostPrinter() = default; - SParametersPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_expected_rows); + SParametersPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); void AddMeasurement(double freq, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const WavePortOperator &wave_port_op, const IoData &iodata); diff --git a/palace/drivers/eigensolver.cpp b/palace/drivers/eigensolver.cpp index faecd7ea1..aca04dc8d 100644 --- a/palace/drivers/eigensolver.cpp +++ b/palace/drivers/eigensolver.cpp @@ -41,7 +41,7 @@ EigenSolver::Solve(const std::vector> &mesh) const // Configure objects for postprocessing. PostOperator post_op(iodata, space_op, "eigenmode"); - PostprocessPrintResults post_results(root, post_dir, post_op, space_op, + PostprocessPrintResults post_results(post_dir, post_op, space_op, iodata.solver.eigenmode.n_post); ComplexVector E(Curl.Width()), B(Curl.Height()); E.UseDevice(true); @@ -351,7 +351,6 @@ void EigenSolver::EigenPostPrinter::PrintStdoutHeader() eig.table.col_options.min_left_padding = 6; // Separate printing due to printing lead as integer not float - fmt::memory_buffer buf; auto to = [&buf](auto f, auto &&...a) { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; @@ -367,7 +366,7 @@ void EigenSolver::EigenPostPrinter::PrintStdoutHeader() } to("{:s}", eig.table.print_row_separator); - Mpi::Print("{}{}", std::string{buf.data(), buf.size()}, + Mpi::Print("{}{}\n", std::string{buf.data(), buf.size()}, std::string(stdout_int_print_width + 4 * eig.table[1].col_width(), '=')); eig.table.col_options = save_defaults; } @@ -387,7 +386,7 @@ void EigenSolver::EigenPostPrinter::PrintStdoutRow(size_t j) auto to = [&buf](auto f, auto &&...a) { fmt::format_to(std::back_inserter(buf), f, std::forward(a)...); }; - to("{:{}d}", int(eig.table[0].data[j]), stdout_int_print_width); + to("{:{}d}", int(eig.table[0].data.at(j)), stdout_int_print_width); for (int i = 1; i < eig.table.n_cols(); i++) { if (i > 0) @@ -397,18 +396,18 @@ void EigenSolver::EigenPostPrinter::PrintStdoutRow(size_t j) to("{:s}", eig.table[i].format_row(j)); } to("{:s}", eig.table.print_row_separator); - Mpi::Print("{}", buf); + Mpi::Print("{}", fmt::to_string(buf)); eig.table.col_options = save_defaults; } -EigenSolver::EigenPostPrinter::EigenPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, int n_post) - : root_{root}, do_measurement_(do_measurement), +EigenSolver::EigenPostPrinter::EigenPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, int n_post) + : root_(Mpi::Root(space_op.GetComm())), stdout_int_print_width(1 + static_cast(std::log10(n_post))) { // Note: we switch to n_eig rather than n_conv for padding since we don't know n_conv // until solve - if (!do_measurement_ || !root_) + if (!root_) { return; } @@ -428,7 +427,7 @@ void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, double error_bkwd, double error_abs, const IoData &iodata) { - if (!do_measurement_ || !root_) + if (!root_) { return; } @@ -450,16 +449,12 @@ void EigenSolver::EigenPostPrinter::AddMeasurement(int eigen_print_idx, PrintStdoutRow(eig.table.n_rows() - 1); } -EigenSolver::PortsPostPrinter::PortsPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, +EigenSolver::PortsPostPrinter::PortsPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, int n_expected_rows) - : root_{root}, do_measurement_{ - do_measurement // - && (lumped_port_op.Size() > 0) // - } { - if (!do_measurement_ || !root_) + const auto &lumped_port_op = space_op.GetLumpedPortOp(); + if (lumped_port_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -489,7 +484,7 @@ void EigenSolver::PortsPostPrinter::AddMeasurement(int eigen_print_idx, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_ || !root_) + if (lumped_port_op.Size() == 0 || !Mpi::Root(post_op.GetComm())) { return; } @@ -518,38 +513,31 @@ void EigenSolver::PortsPostPrinter::AddMeasurement(int eigen_print_idx, port_I.AppendRow(); } -EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, - const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, +EigenSolver::EPRPostPrinter::EPRPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, int n_expected_rows) - : root_{root}, do_measurement_EPR_(do_measurement // - && lumped_port_op.Size() > 0), - do_measurement_Q_(do_measurement_EPR_) { + const auto &lumped_port_op = space_op.GetLumpedPortOp(); // Mode EPR for lumped inductor elements: - for (const auto &[idx, data] : lumped_port_op) { - if (std::abs(data.L) > 0.) + if (std::abs(data.L) > 0.0) { ports_with_L.push_back(idx); } - if (std::abs(data.R) > 0.) + if (std::abs(data.R) > 0.0) { ports_with_R.push_back(idx); } } - do_measurement_EPR_ = do_measurement_EPR_ && !ports_with_L.empty(); - do_measurement_Q_ = do_measurement_Q_ && !ports_with_R.empty(); - - if (!root_ || (!do_measurement_EPR_ && !do_measurement_Q_)) + if ((ports_with_L.empty() && ports_with_R.empty()) || !Mpi::Root(space_op.GetComm())) { return; } using fmt::format; - if (do_measurement_EPR_) + if (!ports_with_L.empty()) { port_EPR = TableWithCSVFile(post_dir / "port-EPR.csv"); port_EPR.table.reserve(n_expected_rows, 1 + ports_with_L.size()); @@ -560,7 +548,7 @@ EigenSolver::EPRPostPrinter::EPRPostPrinter(bool do_measurement, bool root, } port_EPR.AppendHeader(); } - if (do_measurement_Q_) + if (!ports_with_R.empty()) { port_Q = TableWithCSVFile(post_dir / "port-Q.csv"); port_Q.table.reserve(n_expected_rows, 1 + ports_with_R.size()); @@ -578,7 +566,7 @@ void EigenSolver::EPRPostPrinter::AddMeasurementEPR( double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_EPR_ || !root_) + if (ports_with_L.empty() || !Mpi::Root(post_op.GetComm())) { return; } @@ -602,7 +590,7 @@ void EigenSolver::EPRPostPrinter::AddMeasurementQ(double eigen_print_idx, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_Q_ || !root_) + if (ports_with_R.empty() || !Mpi::Root(post_op.GetComm())) { return; } @@ -635,17 +623,14 @@ void EigenSolver::EPRPostPrinter::AddMeasurement(double eigen_print_idx, AddMeasurementQ(eigen_print_idx, post_op, lumped_port_op, iodata); } -EigenSolver::PostprocessPrintResults::PostprocessPrintResults(bool root, - const fs::path &post_dir, +EigenSolver::PostprocessPrintResults::PostprocessPrintResults(const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op, "m", n_post}, - surfaces{true, root, post_dir, post_op, "m", n_post}, - probes{true, root, post_dir, post_op, "m", n_post}, eigen{true, root, post_dir, n_post}, - epr{true, root, post_dir, space_op.GetLumpedPortOp(), n_post}, - error_indicator{true, root, post_dir} + domains{post_dir, post_op, "m", n_post}, surfaces{post_dir, post_op, "m", n_post}, + probes{post_dir, post_op, "m", n_post}, eigen{post_dir, space_op, n_post}, + epr{post_dir, space_op, n_post}, error_indicator{post_dir} { } @@ -656,16 +641,15 @@ void EigenSolver::PostprocessPrintResults::PostprocessStep(const IoData &iodata, double error_bkward) { int eigen_print_idx = step + 1; - domains.AddMeasurement(eigen_print_idx, post_op, iodata); surfaces.AddMeasurement(eigen_print_idx, post_op, iodata); probes.AddMeasurement(eigen_print_idx, post_op, iodata); eigen.AddMeasurement(eigen_print_idx, post_op, error_bkward, error_abs, iodata); epr.AddMeasurement(eigen_print_idx, post_op, space_op.GetLumpedPortOp(), iodata); + // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && step < n_post) { - Mpi::Print("\n"); post_op.WriteFields(step, eigen_print_idx); Mpi::Print(" Wrote mode {:d} to disk\n", eigen_print_idx); } diff --git a/palace/drivers/eigensolver.hpp b/palace/drivers/eigensolver.hpp index 81fec562c..c42f301a3 100644 --- a/palace/drivers/eigensolver.hpp +++ b/palace/drivers/eigensolver.hpp @@ -27,7 +27,6 @@ class EigenSolver : public BaseSolver struct EigenPostPrinter { bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile eig; // Print data to stdout with custom table formatting @@ -37,49 +36,39 @@ class EigenSolver : public BaseSolver public: int stdout_int_print_width = 0; - EigenPostPrinter() = default; - EigenPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, int n_post); + EigenPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, int n_post); void AddMeasurement(int eigen_print_idx, const PostOperator &post_op, double error_bkwd, double error_abs, const IoData &iodata); }; class PortsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile port_V; TableWithCSVFile port_I; public: - PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, int n_expected_rows); + PortsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); void AddMeasurement(int eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; - // Common domain postprocessing for all simulation types. class EPRPostPrinter { - bool root_ = false; - bool do_measurement_EPR_ = false; - bool do_measurement_Q_ = false; TableWithCSVFile port_EPR; TableWithCSVFile port_Q; std::vector ports_with_L; std::vector ports_with_R; - - public: - EPRPostPrinter() = default; - EPRPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, int n_expected_rows); - void AddMeasurementEPR(double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); void AddMeasurementQ(double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); + public: + EPRPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); + void AddMeasurement(double eigen_print_idx, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; @@ -97,9 +86,8 @@ class EigenSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, - const PostOperator &post_op, const SpaceOperator &space_op, - int n_post_); + PostprocessPrintResults(const fs::path &post_dir, const PostOperator &post_op, + const SpaceOperator &space_op, int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, int step, double error_abs, double error_bkward); diff --git a/palace/drivers/electrostaticsolver.cpp b/palace/drivers/electrostaticsolver.cpp index 5efa5af9a..40e984443 100644 --- a/palace/drivers/electrostaticsolver.cpp +++ b/palace/drivers/electrostaticsolver.cpp @@ -40,7 +40,7 @@ ElectrostaticSolver::Solve(const std::vector> &mesh) const PostOperator post_op(iodata, laplace_op, "electrostatic"); int n_step = static_cast(laplace_op.GetSources().size()); MFEM_VERIFY(n_step > 0, "No terminal boundaries specified for electrostatic simulation!"); - PostprocessPrintResults post_results(root, post_dir, post_op, + PostprocessPrintResults post_results(post_dir, post_op, iodata.solver.electrostatic.n_post); // Right-hand side term and solution vector storage. @@ -202,12 +202,10 @@ void ElectrostaticSolver::PostprocessTerminals( } ElectrostaticSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) + const fs::path &post_dir, const PostOperator &post_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op, "i", n_post}, - surfaces{true, root, post_dir, post_op, "i", n_post}, - probes{true, root, post_dir, post_op, "i", n_post}, - error_indicator{true, root, post_dir} + domains{post_dir, post_op, "i", n_post}, surfaces{post_dir, post_op, "i", n_post}, + probes{post_dir, post_op, "i", n_post}, error_indicator{post_dir} { } @@ -217,6 +215,7 @@ void ElectrostaticSolver::PostprocessPrintResults::PostprocessStep( domains.AddMeasurement(idx, post_op, iodata); surfaces.AddMeasurement(idx, post_op, iodata); probes.AddMeasurement(idx, post_op, iodata); + // The internal GridFunctions in PostOperator have already been set from V: if (write_paraview_fields && step < n_post) { diff --git a/palace/drivers/electrostaticsolver.hpp b/palace/drivers/electrostaticsolver.hpp index 283d6c540..21f2be2c6 100644 --- a/palace/drivers/electrostaticsolver.hpp +++ b/palace/drivers/electrostaticsolver.hpp @@ -42,8 +42,8 @@ class ElectrostaticSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, - const PostOperator &post_op, int n_post_); + PostprocessPrintResults(const fs::path &post_dir, const PostOperator &post_op, + int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, int idx); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); diff --git a/palace/drivers/magnetostaticsolver.cpp b/palace/drivers/magnetostaticsolver.cpp index f4321daf2..221596cae 100644 --- a/palace/drivers/magnetostaticsolver.cpp +++ b/palace/drivers/magnetostaticsolver.cpp @@ -40,7 +40,7 @@ MagnetostaticSolver::Solve(const std::vector> &mesh) const int n_step = static_cast(curlcurl_op.GetSurfaceCurrentOp().Size()); MFEM_VERIFY(n_step > 0, "No surface current boundaries specified for magnetostatic simulation!"); - PostprocessPrintResults post_results(root, post_dir, post_op, + PostprocessPrintResults post_results(post_dir, post_op, iodata.solver.magnetostatic.n_post); // Source term and solution vector storage. @@ -207,12 +207,10 @@ void MagnetostaticSolver::PostprocessTerminals(PostOperator &post_op, } MagnetostaticSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const fs::path &post_dir, const PostOperator &post_op, int n_post_) + const fs::path &post_dir, const PostOperator &post_op, int n_post_) : n_post(n_post_), write_paraview_fields(n_post_ > 0), - domains{true, root, post_dir, post_op, "i", n_post}, - surfaces{true, root, post_dir, post_op, "i", n_post}, - probes{true, root, post_dir, post_op, "i", n_post}, - error_indicator{true, root, post_dir} + domains{post_dir, post_op, "i", n_post}, surfaces{post_dir, post_op, "i", n_post}, + probes{post_dir, post_op, "i", n_post}, error_indicator{post_dir} { } @@ -222,6 +220,7 @@ void MagnetostaticSolver::PostprocessPrintResults::PostprocessStep( domains.AddMeasurement(idx, post_op, iodata); surfaces.AddMeasurement(idx, post_op, iodata); probes.AddMeasurement(idx, post_op, iodata); + // The internal GridFunctions in PostOperator have already been set from A: if (write_paraview_fields && step < n_post) { diff --git a/palace/drivers/magnetostaticsolver.hpp b/palace/drivers/magnetostaticsolver.hpp index 608526c24..d8e660a72 100644 --- a/palace/drivers/magnetostaticsolver.hpp +++ b/palace/drivers/magnetostaticsolver.hpp @@ -34,8 +34,8 @@ class MagnetostaticSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, - const PostOperator &post_op, int n_post_); + PostprocessPrintResults(const fs::path &post_dir, const PostOperator &post_op, + int n_post_); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, int step, int idx); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); diff --git a/palace/drivers/transientsolver.cpp b/palace/drivers/transientsolver.cpp index b1f15d2de..ac3746f00 100644 --- a/palace/drivers/transientsolver.cpp +++ b/palace/drivers/transientsolver.cpp @@ -38,7 +38,7 @@ TransientSolver::Solve(const std::vector> &mesh) const // Time stepping is uniform in the time domain. Index sets are for computing things like // port voltages and currents in postprocessing. PostOperator post_op(iodata, space_op, "transient"); - PostprocessPrintResults post_results(root, post_dir, post_op, space_op, n_step, + PostprocessPrintResults post_results(post_dir, post_op, space_op, n_step, iodata.solver.transient.delta_post); { @@ -244,18 +244,12 @@ int TransientSolver::GetNumSteps(double start, double end, double delta) const (delta > 0.0 && dfinal - end < delta_eps * end)); } -// ----------------- -// Measurements / Postprocessing - -TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter( - bool do_measurement, bool root, const fs::path &post_dir, - const SurfaceCurrentOperator &surf_j_op, int n_expected_rows) - : root_{root}, // - do_measurement_(do_measurement // - && (surf_j_op.Size() > 0) // Needs surface currents - ) +TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, + int n_expected_rows) { - if (!do_measurement_ || !root_) + const auto &surf_j_op = space_op.GetSurfaceCurrentOp(); + if (surf_j_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -271,10 +265,12 @@ TransientSolver::CurrentsPostPrinter::CurrentsPostPrinter( surface_I.AppendHeader(); } -void TransientSolver::CurrentsPostPrinter::AddMeasurement( - double t, double J_coef, const SurfaceCurrentOperator &surf_j_op, const IoData &iodata) +void TransientSolver::CurrentsPostPrinter::AddMeasurement(double t, double J_coef, + const SpaceOperator &space_op, + const IoData &iodata) { - if (!do_measurement_ || !root_) + const auto &surf_j_op = space_op.GetSurfaceCurrentOp(); + if (surf_j_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -290,15 +286,12 @@ void TransientSolver::CurrentsPostPrinter::AddMeasurement( surface_I.AppendRow(); } -TransientSolver::PortsPostPrinter::PortsPostPrinter( - bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, int n_expected_rows) - : root_{root}, do_measurement_{ - do_measurement // - && (lumped_port_op.Size() > 0) // Only works for lumped ports - } +TransientSolver::PortsPostPrinter::PortsPostPrinter(const fs::path &post_dir, + const SpaceOperator &space_op, + int n_expected_rows) { - if (!do_measurement_ || !root_) + const auto &lumped_port_op = space_op.GetLumpedPortOp(); + if (lumped_port_op.Size() == 0 || !Mpi::Root(space_op.GetComm())) { return; } @@ -329,7 +322,7 @@ void TransientSolver::PortsPostPrinter::AddMeasurement( double t, double J_coef, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata) { - if (!do_measurement_ || !root_) + if (lumped_port_op.Size() == 0 || !Mpi::Root(post_op.GetComm())) { return; } @@ -369,15 +362,14 @@ void TransientSolver::PortsPostPrinter::AddMeasurement( } TransientSolver::PostprocessPrintResults::PostprocessPrintResults( - bool root, const fs::path &post_dir, const PostOperator &post_op, - const SpaceOperator &space_op, int n_expected_rows, int delta_post_) + const fs::path &post_dir, const PostOperator &post_op, const SpaceOperator &space_op, + int n_expected_rows, int delta_post_) : delta_post{delta_post_}, write_paraview_fields(delta_post_ > 0), - domains{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, - surfaces{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, - currents{true, root, post_dir, space_op.GetSurfaceCurrentOp(), n_expected_rows}, - probes{true, root, post_dir, post_op, "t (ns)", n_expected_rows}, - ports{true, root, post_dir, space_op.GetLumpedPortOp(), n_expected_rows}, - error_indicator{true, root, post_dir} + domains{post_dir, post_op, "t (ns)", n_expected_rows}, + surfaces{post_dir, post_op, "t (ns)", n_expected_rows}, + currents{post_dir, space_op, n_expected_rows}, + probes{post_dir, post_op, "t (ns)", n_expected_rows}, + ports{post_dir, space_op, n_expected_rows}, error_indicator{post_dir} { } @@ -386,12 +378,12 @@ void TransientSolver::PostprocessPrintResults::PostprocessStep( int step, double t, double J_coef) { auto time = iodata.DimensionalizeValue(IoData::ValueType::TIME, t); - domains.AddMeasurement(time, post_op, iodata); surfaces.AddMeasurement(time, post_op, iodata); - currents.AddMeasurement(t, J_coef, space_op.GetSurfaceCurrentOp(), iodata); + currents.AddMeasurement(t, J_coef, space_op, iodata); probes.AddMeasurement(time, post_op, iodata); ports.AddMeasurement(t, J_coef, post_op, space_op.GetLumpedPortOp(), iodata); + // The internal GridFunctions in PostOperator have already been set: if (write_paraview_fields && (step % delta_post == 0)) { diff --git a/palace/drivers/transientsolver.hpp b/palace/drivers/transientsolver.hpp index ee28e0c38..d03cdf2b4 100644 --- a/palace/drivers/transientsolver.hpp +++ b/palace/drivers/transientsolver.hpp @@ -31,29 +31,23 @@ class TransientSolver : public BaseSolver class CurrentsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile surface_I; public: - CurrentsPostPrinter() = default; - CurrentsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const SurfaceCurrentOperator &surf_j_op, int n_expected_rows); - void AddMeasurement(double t, double J_coef, const SurfaceCurrentOperator &surf_j_op, + CurrentsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); + void AddMeasurement(double t, double J_coef, const SpaceOperator &space_op, const IoData &iodata); }; class PortsPostPrinter { - bool root_ = false; - bool do_measurement_ = false; TableWithCSVFile port_V; TableWithCSVFile port_I; public: - PortsPostPrinter() = default; - PortsPostPrinter(bool do_measurement, bool root, const fs::path &post_dir, - const LumpedPortOperator &lumped_port_op, int n_expected_rows); + PortsPostPrinter(const fs::path &post_dir, const SpaceOperator &space_op, + int n_expected_rows); void AddMeasurement(double t, double J_coef, const PostOperator &post_op, const LumpedPortOperator &lumped_port_op, const IoData &iodata); }; @@ -71,9 +65,9 @@ class TransientSolver : public BaseSolver ErrorIndicatorPostPrinter error_indicator; - PostprocessPrintResults(bool is_mpi_root, const fs::path &post_dir, - const PostOperator &post_op, const SpaceOperator &space_op, - int n_expected_rows, int delta_post); + PostprocessPrintResults(const fs::path &post_dir, const PostOperator &post_op, + const SpaceOperator &space_op, int n_expected_rows, + int delta_post); void PostprocessStep(const IoData &iodata, const PostOperator &post_op, const SpaceOperator &space_op, int step, double t, double J_coef); void PostprocessFinal(const PostOperator &post_op, const ErrorIndicator &indicator); diff --git a/palace/models/postoperator.cpp b/palace/models/postoperator.cpp index 0c66abd85..2e1384c71 100644 --- a/palace/models/postoperator.cpp +++ b/palace/models/postoperator.cpp @@ -572,11 +572,6 @@ const PostOperator::InterfaceData &PostOperator::GetInterfaceEFieldEnergy(int id double PostOperator::GetInterfaceParticipation(int idx, double E_m) const { - // Compute the surface dielectric participation ratio and associated quality factor for - // the material interface given by index idx. We have: - // 1/Q_mj = p_mj tan(δ)_j - // with: - // p_mj = 1/2 t_j Re{∫_{Γ_j} (ε_j E_m)ᴴ E_m dS} /(E_elec + E_cap). MFEM_VERIFY(E, "Surface Q not defined, no electric field solution found!"); auto data = GetInterfaceEFieldEnergy(idx); return data.energy / E_m; @@ -789,8 +784,6 @@ void PostOperator::WriteFields(int step, double time) const paraview_bdr.Save(); mesh::NondimensionalizeMesh(mesh, mesh_Lc0); ScaleGridFunctions(1.0 / mesh_Lc0, mesh.Dimension(), HasImag(), E, B, V, A); - - Mpi::Barrier(GetComm()); } void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const @@ -868,8 +861,6 @@ void PostOperator::WriteFieldsFinal(const ErrorIndicator *indicator) const paraview.RegisterVCoeffField(name, gf); } mesh::NondimensionalizeMesh(mesh, mesh_Lc0); - - Mpi::Barrier(GetComm()); } void PostOperator::MeasureProbes() diff --git a/palace/models/postoperator.hpp b/palace/models/postoperator.hpp index dde5e9ec2..ed6ff645d 100644 --- a/palace/models/postoperator.hpp +++ b/palace/models/postoperator.hpp @@ -173,7 +173,7 @@ class PostOperator } // Function that triggers all available post-processing measurements and populate cache. - // If SpaceOperator is provided, will perform port measurements. + // If SpaceOperator is provided, will perform any port measurements. void MeasureAll(); void MeasureAll(const SpaceOperator &space_op); @@ -183,31 +183,16 @@ class PostOperator // Treat the frequency, for driven and eigenmode solvers, as a "measurement", that other // measurements can depend on. This has to be supplied during the solver loop separate // from the fields. - void SetFrequency(double omega) - { - measurement_cache.omega = std::complex(omega); - } - void SetFrequency(std::complex omega) - { - measurement_cache.omega = omega; - } + void SetFrequency(double omega) { measurement_cache.omega = std::complex(omega); } + void SetFrequency(std::complex omega) { measurement_cache.omega = omega; } // Return stored frequency that was given in SetFrequency. - std::complex GetFrequency() const - { - return measurement_cache.omega; - } + std::complex GetFrequency() const { return measurement_cache.omega; } // Postprocess the total electric and magnetic field energies in the electric and magnetic // fields. - double GetEFieldEnergy() const - { - return measurement_cache.domain_E_field_energy_all; - } - double GetHFieldEnergy() const - { - return measurement_cache.domain_H_field_energy_all; - } + double GetEFieldEnergy() const { return measurement_cache.domain_E_field_energy_all; } + double GetHFieldEnergy() const { return measurement_cache.domain_H_field_energy_all; } // Postprocess the electric and magnetic field energies in the domain with the given // index. @@ -242,7 +227,7 @@ class PostOperator return measurement_cache.lumped_port_capacitor_energy; } - // Postprocess the S-parameter for recieving lumped or wave port index using the electric + // Postprocess the S-parameter for receiving lumped or wave port index using the electric // field solution. // TODO: In multi-excitation PR we will guarantee that lumped & wave ports have unique idx // TODO: Merge lumped and wave port S_ij calculations to allow both at same time. @@ -279,10 +264,10 @@ class PostOperator // the internal grid functions are real-valued, the returned fields have only nonzero real // parts. Output vectors are ordered by vector dimension, that is [v1x, v1y, v1z, v2x, // v2y, v2z, ...]. - int GetInterpolationOpVDim() const { return interp_op.GetVDim(); } const auto &GetProbes() const { return interp_op.GetProbes(); } std::vector> ProbeEField() const; std::vector> ProbeBField() const; + int GetInterpolationOpVDim() const { return interp_op.GetVDim(); } // Get the associated MPI communicator. MPI_Comm GetComm() const diff --git a/palace/utils/tablecsv.hpp b/palace/utils/tablecsv.hpp index f99bd3df3..0e173facd 100644 --- a/palace/utils/tablecsv.hpp +++ b/palace/utils/tablecsv.hpp @@ -102,7 +102,7 @@ class Column class Table { // Column-wise mini-table for storing data and and printing to csv file for doubles. - // Future: allow int and other output, allow non-owning memeory via span + // Future: allow int and other output, allow non-owning memory via span std::vector cols; // Cache value to reserve vector space by default