Skip to content

Commit 9de5da3

Browse files
committed
addOperatorSparsity(J, mat) unit tests
Should apply the connectivity pattern to all pairs of variables in the system Jacobian.
1 parent ba6996e commit 9de5da3

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

include/bout/petsc_jacobian.hxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
/// @param in_var Column variable index in [0, nvars).
2828
void addOperatorSparsity(Mat Jfd, Mat sub, int out_var, int in_var);
2929

30+
/// @param Jfd The Jacobian matrix to populate. Must be preallocated.
31+
/// @param sub Evolving-cell submatrix providing the nonzero pattern.
32+
void addOperatorSparsity(Mat Jfd, Mat sub);
33+
3034
#endif // BOUT_HAS_PETSC
3135

3236
#endif // BOUT_PETSC_JACOBIAN_H

src/mesh/petsc_jacobian.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ void addOperatorSparsity(Mat Jfd, Mat sub, int out_var, int in_var) {
4545
}
4646
}
4747

48+
void addOperatorSparsity(Mat Jfd, Mat sub) {}
49+
4850
#endif // BOUT_HAS_PETSC

tests/unit/mesh/test_petsc_jacobian.cxx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <petscmat.h>
3030

3131
#include <algorithm>
32+
#include <set>
3233
#include <utility>
3334
#include <vector>
3435

@@ -446,4 +447,192 @@ TEST_F(PetscAddSparsityTest, first_and_last_variable_blocks_correct) {
446447
MatDestroy(&sub);
447448
}
448449

450+
// ============================================================================
451+
// all_pairs_single_variable
452+
//
453+
// With nvars=1 the all-pairs overload is identical to addOperatorSparsity
454+
// with out_var=0, in_var=0.
455+
// ============================================================================
456+
TEST_F(PetscAddSparsityTest, all_pairs_single_variable) {
457+
const PetscInt n_sub = 3;
458+
const PetscInt nlocal_sub = n_sub;
459+
const int nvars = 1;
460+
461+
const std::vector<std::pair<PetscInt, PetscInt>> sub_entries = {{0, 0}, {1, 1}, {2, 2}};
462+
Mat sub = buildPatternMat(n_sub, nlocal_sub, 0, sub_entries);
463+
464+
auto [jfd, jfd_nlocal, jfd_rstart] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
465+
466+
addOperatorSparsity(jfd, sub);
467+
MatAssemblyBegin(jfd, MAT_FINAL_ASSEMBLY);
468+
MatAssemblyEnd(jfd, MAT_FINAL_ASSEMBLY);
469+
470+
const auto nzs = matNonzeroPositions(jfd);
471+
ASSERT_EQ(sub_entries.size(), nzs.size());
472+
473+
MatDestroy(&sub);
474+
MatDestroy(&jfd);
475+
}
476+
477+
// ============================================================================
478+
// all_pairs_fills_every_variable_block
479+
//
480+
// With nvars=3 every one of the 9 variable blocks must contain exactly the
481+
// same pattern as sub. The total nonzero count must equal
482+
// nvars * nvars * nnz(sub).
483+
// ============================================================================
484+
TEST_F(PetscAddSparsityTest, all_pairs_fills_every_variable_block) {
485+
const PetscInt n_sub = 3;
486+
const PetscInt nlocal_sub = n_sub;
487+
const int nvars = 3;
488+
489+
const std::vector<std::pair<PetscInt, PetscInt>> sub_entries = {{0, 0}, {1, 1}, {2, 2}};
490+
Mat sub = buildPatternMat(n_sub, nlocal_sub, 0, sub_entries);
491+
492+
auto [jfd, jfd_nlocal, jfd_rstart] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
493+
494+
addOperatorSparsity(jfd, sub);
495+
MatAssemblyBegin(jfd, MAT_FINAL_ASSEMBLY);
496+
MatAssemblyEnd(jfd, MAT_FINAL_ASSEMBLY);
497+
498+
const auto nzs = matNonzeroPositions(jfd);
499+
const std::size_t expected_total =
500+
static_cast<std::size_t>(nvars * nvars) * sub_entries.size();
501+
ASSERT_EQ(expected_total, nzs.size());
502+
503+
// Every (out_var, in_var) pair must appear exactly once per sub entry.
504+
// Count how many Jfd entries land in each variable block.
505+
std::vector<std::vector<int>> block_counts(nvars, std::vector<int>(nvars, 0));
506+
for (const auto& [r, c] : nzs) {
507+
block_counts[r % nvars][c % nvars]++;
508+
}
509+
for (int ov = 0; ov < nvars; ++ov) {
510+
for (int iv = 0; iv < nvars; ++iv) {
511+
EXPECT_EQ(static_cast<int>(sub_entries.size()), block_counts[ov][iv])
512+
<< "Block (" << ov << "," << iv << ") has wrong entry count";
513+
}
514+
}
515+
516+
MatDestroy(&sub);
517+
MatDestroy(&jfd);
518+
}
519+
520+
// ============================================================================
521+
// all_pairs_entries_match_single_pair_calls
522+
//
523+
// The all-pairs overload must produce exactly the same Jfd nonzero positions
524+
// as calling the single-pair overload for every (out_var, in_var).
525+
// ============================================================================
526+
TEST_F(PetscAddSparsityTest, all_pairs_entries_match_single_pair_calls) {
527+
const PetscInt n_sub = 3;
528+
const PetscInt nlocal_sub = n_sub;
529+
const int nvars = 2;
530+
531+
// Non-trivial pattern so the stride mapping is exercised
532+
const std::vector<std::pair<PetscInt, PetscInt>> sub_entries = {
533+
{0, 0}, {0, 2}, {1, 1}, {2, 0}, {2, 2}};
534+
Mat sub = buildPatternMat(n_sub, nlocal_sub, 0, sub_entries);
535+
536+
// Build expected positions by calling the single-pair version for each block
537+
std::vector<std::pair<PetscInt, PetscInt>> expected;
538+
{
539+
auto [jfd_ref, nlocal_ref, rstart_ref] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
540+
for (int ov = 0; ov < nvars; ++ov) {
541+
for (int iv = 0; iv < nvars; ++iv) {
542+
addOperatorSparsity(jfd_ref, sub, ov, iv);
543+
}
544+
}
545+
MatAssemblyBegin(jfd_ref, MAT_FINAL_ASSEMBLY);
546+
MatAssemblyEnd(jfd_ref, MAT_FINAL_ASSEMBLY);
547+
expected = matNonzeroPositions(jfd_ref);
548+
MatDestroy(&jfd_ref);
549+
}
550+
551+
// Build actual positions using the all-pairs overload
552+
std::vector<std::pair<PetscInt, PetscInt>> actual;
553+
{
554+
auto [jfd_act, nlocal_act, rstart_act] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
555+
addOperatorSparsity(jfd_act, sub);
556+
MatAssemblyBegin(jfd_act, MAT_FINAL_ASSEMBLY);
557+
MatAssemblyEnd(jfd_act, MAT_FINAL_ASSEMBLY);
558+
actual = matNonzeroPositions(jfd_act);
559+
MatDestroy(&jfd_act);
560+
}
561+
562+
std::sort(expected.begin(), expected.end());
563+
std::sort(actual.begin(), actual.end());
564+
565+
ASSERT_EQ(expected.size(), actual.size());
566+
for (std::size_t k = 0; k < expected.size(); ++k) {
567+
EXPECT_EQ(expected[k], actual[k]) << "Mismatch at position " << k;
568+
}
569+
570+
MatDestroy(&sub);
571+
}
572+
573+
// ============================================================================
574+
// all_pairs_empty_sub_fills_nothing
575+
//
576+
// An empty sub must leave Jfd empty regardless of nvars.
577+
// ============================================================================
578+
TEST_F(PetscAddSparsityTest, all_pairs_empty_sub_fills_nothing) {
579+
const PetscInt n_sub = 4;
580+
const PetscInt nlocal_sub = n_sub;
581+
const int nvars = 3;
582+
583+
Mat sub = buildPatternMat(n_sub, nlocal_sub, 0, {});
584+
585+
auto [jfd, jfd_nlocal, jfd_rstart] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
586+
587+
addOperatorSparsity(jfd, sub);
588+
MatAssemblyBegin(jfd, MAT_FINAL_ASSEMBLY);
589+
MatAssemblyEnd(jfd, MAT_FINAL_ASSEMBLY);
590+
591+
const auto nzs = matNonzeroPositions(jfd);
592+
EXPECT_EQ(0U, nzs.size());
593+
594+
MatDestroy(&sub);
595+
MatDestroy(&jfd);
596+
}
597+
598+
// ============================================================================
599+
// all_pairs_no_entries_outside_expected_positions
600+
//
601+
// Every entry in Jfd must lie at a position consistent with the stride formula
602+
// applied to some (out_var, in_var) pair. Specifically, for each entry
603+
// (r, c) in Jfd there must exist a (sr, sc) in sub such that
604+
// r == sr * nvars + (r % nvars) and c == sc * nvars + (c % nvars).
605+
// ============================================================================
606+
TEST_F(PetscAddSparsityTest, all_pairs_no_entries_outside_expected_positions) {
607+
const PetscInt n_sub = 3;
608+
const PetscInt nlocal_sub = n_sub;
609+
const int nvars = 2;
610+
611+
std::vector<std::pair<PetscInt, PetscInt>> sub_entries = {
612+
{0, 1}, {1, 0}, {1, 2}, {2, 2}};
613+
Mat sub = buildPatternMat(n_sub, nlocal_sub, 0, sub_entries);
614+
615+
// Pre-compute sub cell pairs for fast lookup
616+
const std::set<std::pair<PetscInt, PetscInt>> sub_set(sub_entries.begin(),
617+
sub_entries.end());
618+
619+
auto [jfd, jfd_nlocal, jfd_rstart] = buildEmptyJfd(n_sub, nlocal_sub, 0, nvars);
620+
621+
addOperatorSparsity(jfd, sub);
622+
MatAssemblyBegin(jfd, MAT_FINAL_ASSEMBLY);
623+
MatAssemblyEnd(jfd, MAT_FINAL_ASSEMBLY);
624+
625+
const auto nzs = matNonzeroPositions(jfd);
626+
for (const auto& [r, c] : nzs) {
627+
const PetscInt sr = r / nvars;
628+
const PetscInt sc = c / nvars;
629+
EXPECT_TRUE(sub_set.count({sr, sc}) > 0)
630+
<< "Entry (" << r << "," << c << ") maps to sub cell (" << sr << "," << sc
631+
<< ") which is not in sub";
632+
}
633+
634+
MatDestroy(&sub);
635+
MatDestroy(&jfd);
636+
}
637+
449638
#endif // BOUT_HAS_PETSC

0 commit comments

Comments
 (0)