|
29 | 29 | #include <petscmat.h> |
30 | 30 |
|
31 | 31 | #include <algorithm> |
| 32 | +#include <set> |
32 | 33 | #include <utility> |
33 | 34 | #include <vector> |
34 | 35 |
|
@@ -446,4 +447,192 @@ TEST_F(PetscAddSparsityTest, first_and_last_variable_blocks_correct) { |
446 | 447 | MatDestroy(&sub); |
447 | 448 | } |
448 | 449 |
|
| 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 | + |
449 | 638 | #endif // BOUT_HAS_PETSC |
0 commit comments