Skip to content

Commit 1b98be6

Browse files
committed
Always use refined Gram-Schmidt in RomOp with circuit synthesis
1 parent ac78339 commit 1b98be6

File tree

1 file changed

+69
-18
lines changed

1 file changed

+69
-18
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

0 commit comments

Comments
 (0)