Skip to content

Commit f94f27f

Browse files
committed
Add both dual and primary vector to PROM
- Add orthogonality tolerance in UpdatePROM - Always use refined Gram-Schmidt in RomOp with circuit synthesis
1 parent a0a5e8d commit f94f27f

File tree

5 files changed

+133
-42
lines changed

5 files changed

+133
-42
lines changed

palace/models/romoperator.cpp

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,12 @@ RomOperator::RomOperator(const IoData &iodata, SpaceOperator &space_op,
328328
std::size_t max_size_per_excitation)
329329
: space_op(space_op), orthog_type(iodata.solver.linear.gs_orthog)
330330
{
331+
// Always use refined CGS with adaptive synthesis for higher accuracy on print-out.
332+
if (iodata.solver.driven.adaptive_circuit_synthesis)
333+
{
334+
orthog_type = Orthogonalization::CGS2;
335+
}
336+
331337
// Construct the system matrices defining the linear operator. PEC boundaries are handled
332338
// simply by setting diagonal entries of the system matrix for the corresponding dofs.
333339
// Because the Dirichlet BC is always homogeneous, no special elimination is required on
@@ -433,29 +439,60 @@ void RomOperator::SolveHDM(int excitation_idx, double omega, ComplexVector &u)
433439

434440
void RomOperator::AddLumpedPortModesForSynthesis(const IoData &iodata)
435441
{
436-
auto &lumped_port_op = space_op.GetLumpedPortOp();
437-
for (const auto &[port_idx, port_data] : lumped_port_op)
442+
// Add modes for lumped port to use them a circuit matrices.
443+
//
444+
// The excitation vector that we expect to add to the PROM is just the primary vector e
445+
// associated with that port. However, when computing the response (scattering) matrix, we
446+
// have an overlap g * e, where g is the boundary bilinear form. For Nédélec elements e
447+
// and g * e are not proportional to each other. This is true on simple tet meshes, for
448+
// simple hex meshes they are equal empirically, but this was not tested generally (e.g.
449+
// on non-conforming meshes).
450+
//
451+
// To preserve orthogonality while also extracting the "full" port profile, we add both to
452+
// the PROM — first g * e and then e (orthogonalized to g * e), which accounts for a
453+
// "finite element correction". When e and g * e are the same, we should only add a single
454+
// vector to the PROM.
455+
456+
// Workspace vector: Lumped Ports are Real, but interface is complex.
457+
ComplexVector vec;
458+
vec.SetSize(space_op.GetNDSpace().GetTrueVSize());
459+
vec.UseDevice(true);
460+
vec = 0.0;
461+
462+
// Add all dual vectors first: we want this port to be orthogonal to the outside world and
463+
// check orthogonality internally.
464+
for (const auto &[port_idx, port_data] : space_op.GetLumpedPortOp())
465+
{
466+
space_op.GetLumpedPortExcitationVectorDual(port_idx, vec, true);
467+
UpdatePROM(vec, fmt::format("port_{:d}", port_idx));
468+
}
469+
470+
// Check that the ports don't have any overlap.
471+
MFEM_VERIFY(GetRomOrthogonalityMatrix().isDiagonal(),
472+
"Lumped port fields on the mesh should have exactly zero overlap. This may "
473+
"be non-zero if attributes share edges.");
474+
475+
// Add primary vector second, since this needs to be orthogonalized to the dual
476+
// "outside" vector.
477+
for (const auto &[port_idx, port_data] : space_op.GetLumpedPortOp())
438478
{
439-
ComplexVector port_excitation_E;
440-
port_excitation_E.UseDevice(true);
441-
space_op.GetLumpedPortExcitationVector(port_idx, port_excitation_E, true);
442-
UpdatePROM(port_excitation_E, fmt::format("port_{:d}", port_idx));
479+
space_op.GetLumpedPortExcitationVectorPrimary(port_idx, vec, true);
480+
// Drop if primary vector is same as dual vector.
481+
auto orthog_condition = ORTHOG_TOL * linalg::Norml2(space_op.GetComm(), vec.Real());
482+
UpdatePROM(vec, fmt::format("port_{:d}_correction", port_idx), orthog_condition);
443483
}
444484

445-
// Debug Print ROM Matrices
485+
// Debug Print
446486
if constexpr (true) // ignore
447487
{
448488
fs::path folder_tmp = fs::path(iodata.problem.output) / "prom_port_debug";
449489
fs::create_directories(folder_tmp);
450490
PrintPROMMatrices(iodata.units, folder_tmp);
451491
}
452-
453-
const auto &orth_R = GetRomOrthogonalityMatrix();
454-
MFEM_VERIFY(orth_R.isDiagonal(), "Lumped port modes should have exactly zero overlap.");
455492
}
456493

457-
458-
void RomOperator::UpdatePROM(const ComplexVector &u, std::string_view node_label)
494+
void RomOperator::UpdatePROM(const ComplexVector &u, std::string_view node_label,
495+
double drop_degenerate_vector_norm_tol)
459496
{
460497
// Update PROM basis V. The basis is always real (each complex solution adds two basis
461498
// vectors, if it has a nonzero real and imaginary parts).
@@ -469,30 +506,44 @@ void RomOperator::UpdatePROM(const ComplexVector &u, std::string_view node_label
469506
const bool has_imag = (norm_im > norm_tol);
470507

471508
const std::size_t dim_V_old = V.size();
472-
const std::size_t dim_V_new =
509+
std::size_t dim_V_new =
473510
V.size() + static_cast<std::size_t>(has_real) + static_cast<std::size_t>(has_imag);
474511

475512
orth_R.conservativeResizeLike(Eigen::MatrixXd::Zero(dim_V_new, dim_V_new));
476513

477-
auto add_real_vector_to_basis = [this](const Vector &vector)
514+
auto add_real_vector_to_basis = [this, drop_degenerate_vector_norm_tol](
515+
const Vector &vector, std::string_view node_label)
478516
{
479517
auto dim_V = V.size();
480518
auto &v = V.emplace_back(vector);
481519
OrthogonalizeColumn(orthog_type, space_op.GetComm(), V, v, orth_R.col(dim_V).data(),
482520
dim_V);
483521
orth_R(dim_V, dim_V) = linalg::Norml2(space_op.GetComm(), v);
522+
if (orth_R(dim_V, dim_V) < drop_degenerate_vector_norm_tol)
523+
{
524+
V.pop_back();
525+
return false;
526+
}
484527
v *= 1.0 / orth_R(dim_V, dim_V);
528+
v_node_label.emplace_back(node_label);
529+
return true;
485530
};
486531

487532
if (has_real)
488533
{
489-
add_real_vector_to_basis(u.Real());
490-
v_node_label.emplace_back(fmt::format("{}_re", node_label));
534+
add_real_vector_to_basis(u.Real(), fmt::format("{}_re", node_label));
491535
}
492536
if (has_imag)
493537
{
494-
add_real_vector_to_basis(u.Imag());
495-
v_node_label.emplace_back(fmt::format("{}_im", node_label));
538+
add_real_vector_to_basis(u.Imag(), fmt::format("{}_im", node_label));
539+
}
540+
541+
// Vectors might have been dropped due to orthogonality check.
542+
dim_V_new = V.size();
543+
orth_R.conservativeResize(dim_V_new, dim_V_new); // Might shrink
544+
if (dim_V_new == dim_V_old)
545+
{
546+
return;
496547
}
497548

498549
// Update reduced-order operators. Resize preserves the upper dim0 x dim0 block of each

palace/models/romoperator.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ class RomOperator
134134
// "node_label". This will be printed in the header of the csv files when printing PROM
135135
// matrices. It is needed to distinguish port and solution field configuration as well as
136136
// to reconstruct if field configuration are pure real, imaginary or complex.
137-
void UpdatePROM(const ComplexVector &u, std::string_view node_label);
137+
void UpdatePROM(const ComplexVector &u, std::string_view node_label,
138+
double drop_degenerate_vector_norm_tol = 0.0);
138139

139140
// Add solution u to the minimal-rational interpolation for error estimation. MRI are
140141
// separated by excitation index.

palace/models/spaceoperator.cpp

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -841,43 +841,79 @@ bool SpaceOperator::GetExcitationVector(int excitation_idx, double omega,
841841
return nnz1 || nnz2;
842842
}
843843

844-
bool SpaceOperator::GetLumpedPortExcitationVector(int port_idx, ComplexVector &RHS,
845-
bool zero_metal)
844+
bool SpaceOperator::GetLumpedPortExcitationVectorPrimary(int port_idx,
845+
ComplexVector &RHS_primary,
846+
bool zero_metal)
846847
{
847-
RHS.SetSize(GetNDSpace().GetTrueVSize());
848-
RHS.UseDevice(true);
849-
RHS = 0.0;
848+
const auto &data = lumped_port_op.GetPort(port_idx);
850849

851-
MFEM_VERIFY(RHS.Size() == GetNDSpace().GetTrueVSize(),
852-
"Invalid T-vector size for AddExcitationVector1Internal!");
853850
SumVectorCoefficient fb(GetMesh().SpaceDimension());
851+
mfem::Array<int> attr_list;
852+
mfem::Array<int> attr_marker;
853+
for (const auto &elem : data.elems)
854+
{
855+
attr_list.Append(elem->GetAttrList());
856+
// TODO: Deal with effective reference normalization
857+
const double Rs = 1.0 * data.GetToSquare(*elem);
858+
const double Hinc = 1.0 / std::sqrt(Rs * elem->GetGeometryWidth() *
859+
elem->GetGeometryLength() * data.elems.size());
860+
fb.AddCoefficient(elem->GetModeCoefficient(Hinc));
861+
}
862+
auto &mesh = GetNDSpace().GetParMesh();
863+
int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
864+
mesh::AttrToMarker(bdr_attr_max, attr_list, attr_marker);
865+
866+
RHS_primary.SetSize(GetNDSpace().GetTrueVSize());
867+
RHS_primary.UseDevice(true);
868+
RHS_primary = 0.0;
854869

870+
GridFunction rhs(GetNDSpace());
871+
rhs = 0.0;
872+
rhs.Real().ProjectBdrCoefficientTangent(fb, attr_marker);
873+
GetNDSpace().GetProlongationMatrix()->AddMultTranspose(rhs.Real(), RHS_primary.Real());
874+
if (zero_metal)
875+
{
876+
linalg::SetSubVector(RHS_primary.Real(), nd_dbc_tdof_lists.back(), 0.0);
877+
}
878+
return true;
879+
}
880+
881+
bool SpaceOperator::GetLumpedPortExcitationVectorDual(int port_idx, ComplexVector &RHS_dual,
882+
bool zero_metal)
883+
{
855884
const auto &data = lumped_port_op.GetPort(port_idx);
856885

886+
SumVectorCoefficient fb(GetMesh().SpaceDimension());
857887
mfem::Array<int> attr_list;
858888
mfem::Array<int> attr_marker;
859-
860889
for (const auto &elem : data.elems)
861890
{
862891
attr_list.Append(elem->GetAttrList());
863-
fb.AddCoefficient(
864-
elem->GetModeCoefficient(1.0 / (elem->GetGeometryWidth() * data.elems.size())));
892+
// TODO: Deal with effective reference normalization
893+
const double Rs = 1.0 * data.GetToSquare(*elem);
894+
const double Hinc = 1.0 / std::sqrt(Rs * elem->GetGeometryWidth() *
895+
elem->GetGeometryLength() * data.elems.size());
896+
fb.AddCoefficient(elem->GetModeCoefficient(Hinc));
865897
}
866898
auto &mesh = GetNDSpace().GetParMesh();
867899
int bdr_attr_max = mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0;
868900
mesh::AttrToMarker(bdr_attr_max, attr_list, attr_marker);
869901

870-
mfem::LinearForm rhs1(&GetNDSpace().Get());
871-
rhs1.AddBoundaryIntegrator(new VectorFEBoundaryLFIntegrator(fb), attr_marker);
872-
rhs1.UseFastAssembly(false);
873-
rhs1.UseDevice(false);
874-
rhs1.Assemble();
875-
rhs1.UseDevice(true);
876-
GetNDSpace().GetProlongationMatrix()->AddMultTranspose(rhs1, RHS.Real());
902+
RHS_dual.SetSize(GetNDSpace().GetTrueVSize());
903+
RHS_dual.UseDevice(true);
904+
RHS_dual = 0.0;
905+
906+
mfem::LinearForm rhs(&GetNDSpace().Get());
907+
rhs.AddBoundaryIntegrator(new VectorFEBoundaryLFIntegrator(fb), attr_marker);
908+
rhs.UseFastAssembly(false);
909+
rhs.UseDevice(false);
910+
rhs.Assemble();
911+
rhs.UseDevice(true);
912+
GetNDSpace().GetProlongationMatrix()->Mult(rhs, RHS_dual.Real());
877913

878914
if (zero_metal)
879915
{
880-
linalg::SetSubVector(RHS.Real(), nd_dbc_tdof_lists.back(), 0.0);
916+
linalg::SetSubVector(RHS_dual.Real(), nd_dbc_tdof_lists.back(), 0.0);
881917
}
882918
return true;
883919
}
@@ -934,8 +970,8 @@ bool SpaceOperator::AddExcitationVector1Internal(int excitation_idx, Vector &RHS
934970
bool SpaceOperator::AddExcitationVector2Internal(int excitation_idx, double omega,
935971
ComplexVector &RHS2)
936972
{
937-
// Assemble the contribution of wave ports to the frequency domain excitation term at the
938-
// specified frequency.
973+
// Assemble the contribution of wave ports to the frequency domain excitation term at
974+
// the specified frequency.
939975
MFEM_VERIFY(RHS2.Size() == GetNDSpace().GetTrueVSize(),
940976
"Invalid T-vector size for AddExcitationVector2Internal!");
941977
SumVectorCoefficient fbr(GetMesh().SpaceDimension()), fbi(GetMesh().SpaceDimension());

palace/models/spaceoperator.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,16 @@ class SpaceOperator
215215
bool GetExcitationVector(int excitation_idx, Vector &RHS);
216216
bool GetExcitationVector(int excitation_idx, double omega, ComplexVector &RHS);
217217

218-
bool GetLumpedPortExcitationVector(int port_idx, ComplexVector &RHS, bool zero_metal);
219-
220218
// Separate out RHS vector as RHS = iω RHS1 + RHS2(ω). The return value indicates whether
221219
// or not the excitation is nonzero (and thus is true most of the time).
222220
bool GetExcitationVector1(int excitation_idx, ComplexVector &RHS1);
223221
bool GetExcitationVector2(int excitation_idx, double omega, ComplexVector &RHS2);
224222

223+
bool GetLumpedPortExcitationVectorPrimary(int port_idx, ComplexVector &RHS_primary,
224+
bool zero_metal);
225+
bool GetLumpedPortExcitationVectorDual(int port_idx, ComplexVector &RHS_dual,
226+
bool zero_metal);
227+
225228
// Construct a constant or randomly initialized vector which satisfies the PEC essential
226229
// boundary conditions.
227230
void GetRandomInitialVector(ComplexVector &v);

test/unit/test-drivensolver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ TEST_CASE("PortOrthogonalityAdaptivePROM", "[driven_solver][Serial][Parallel]")
6363
utils::ConfigureOmp(), ""};
6464
CHECK_THROWS(solver.SolveEstimateMarkRefine(mesh_),
6565
Catch::Matchers::ContainsSubstring(
66-
"Lumped port modes should have exactly zero overlap"));
66+
"should have exactly zero overlap"));
6767
}

0 commit comments

Comments
 (0)