Skip to content

FE Quadrature Phase 01 abstract contract - #593

Draft
zasexton wants to merge 39 commits into
SimVascular:mainfrom
zasexton:quadrature-P01
Draft

FE Quadrature Phase 01 abstract contract#593
zasexton wants to merge 39 commits into
SimVascular:mainfrom
zasexton:quadrature-P01

Conversation

@zasexton

Copy link
Copy Markdown
Collaborator

Summary

This pull request establishes Phase 01 of the finite-element Quadrature module.
It defines the immutable reference-rule contract that concrete rule generation,
selection, and consumer migration can build on in later phases.

Related to #580.

The change:

  • Adds an abstract, immutable QuadratureRule interface for canonical
    reference cells.
  • Defines ordered point/weight storage using the FE fixed-size vector type.
  • Exposes cell family, reference dimension, polynomial exactness,
    reference-cell measure, and const sample access.
  • Establishes one construction-time validation boundary for provider data.
  • Adds exact, order-independent binary64 validation of the weight sum.
  • Adds focused unit coverage for the new representation, validation boundary,
    and immutable query interface.
  • Registers the Quadrature sources with the solver build and the module with
    the FE documentation hierarchy.

Design

Immutable rule contract

QuadratureRule owns one complete set of ordered reference points and weights.
Successful construction establishes the invariant, after which consumers use
only const queries. Rule objects are non-copyable and non-movable and are
intended to be created once and shared through const ownership.

Concrete providers construct a protected RuleData payload containing:

  • Declared total-degree polynomial exactness.
  • Ordered canonical reference points.
  • Corresponding ordered weights.

The base class derives dimension and reference-cell measure from CellFamily,
preventing providers from supplying redundant topology metadata.

Supported reference cells

The Phase 01 contract covers:

  • Point
  • Line
  • Triangle
  • Quadrilateral
  • Tetrahedron
  • Hexahedron
  • Wedge

Pyramid, polygon, polyhedron, and unknown families remain explicitly
unsupported.

Construction validation

Construction rejects:

  • Unsupported cell families.
  • Negative polynomial exactness.
  • Empty or mismatched point/weight storage.
  • Non-finite coordinates or weights.
  • Nonzero inactive coordinates.
  • Points outside the declared canonical reference cell.
  • Weights that do not reproduce the reference-cell measure within the scaled
    validation tolerance.

Duplicate points and zero or negative individual weights remain admissible when
all other invariants hold.

The weight sum is evaluated exactly from the stored binary64 values using a
fixed-storage accumulator. Validation is therefore independent of point/weight
ordering, cancellation, intermediate floating-point overflow, and the host
long double representation. Compile-time checks document and enforce the
binary64 value model and object layout required by that accumulator; this
will be important for cross-platform compile time validation.

Phase 01 regression tests

The Phase 01 tests are intentionally limited to the new Quadrature
infrastructure. They verify:

  • The fixed-size QuadPoint representation.
  • The immutable QuadratureRule query interface.
  • Supported and unsupported reference-cell families.
  • Metadata and point/weight storage invariants.
  • Finite values, inactive coordinates, and canonical reference-cell
    containment.
  • Reference-cell measure validation, including admissible negative weights.
  • Exact binary64 weight summation under cancellation, subnormal values,
    reordered inputs, and large rules.
  • Construction tolerances for reference coordinates and weight normalization.

These tests do not exercise existing quadrature tables, solver-specific selection
paths, or integration consumers.

Supporting changes

  • Removes the unused speculative QuadratureType enumeration from the shared
    FE vocabulary.
  • Adds FE/Quadrature sources to the solver target.
  • Adds the Quadrature module to the top-level FE documentation hierarchy.
  • Runs Doxygen from the repository root and removes stale generated output
    before documentation generation, ensuring relative paths resolve
    consistently.

Scope and non-goals

This pull request intentionally does not:

  • Add production concrete quadrature providers.
  • Add a quadrature factory, cache, or rule-selection policy.
  • Migrate solver containers or integration consumers.
  • Change basis selection, reduced-integration policy, or physical-space
    mapping.

Validation performed

  • GCC build of run_all_unit_tests and svmultiphysics.
  • GCC unit suite: 245 of 245 tests passed.
  • Doxygen validation with no Quadrature-specific warnings.

zasexton and others added 30 commits June 9, 2025 10:45
updating fork repo with upstream head
update main to upstream branch
@zasexton

Copy link
Copy Markdown
Collaborator Author

@ktbolt @michelebucelli let me know what you think of the abstract class contract for quadrature. I believe the public API for this contract is minimal and clean while ensuring that explicit Quadrature rules that are created can be validated. As always, happy to have more design input here and will incorporate changes pretty quickly so we can begin making a concrete QuadratureRule class.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.67782% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.82%. Comparing base (b5fbed0) to head (970f3c0).

Files with missing lines Patch % Lines
...s/unitTests/FE/Quadrature/test_QuadratureRules.cpp 88.69% 32 Missing ⚠️
...ode/Source/solver/FE/Quadrature/QuadratureRule.cpp 98.37% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #593      +/-   ##
==========================================
+ Coverage   72.54%   72.82%   +0.27%     
==========================================
  Files         252      255       +3     
  Lines       39032    39511     +479     
  Branches     6678     6710      +32     
==========================================
+ Hits        28317    28775     +458     
- Misses      10480    10500      +20     
- Partials      235      236       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@zasexton zasexton added the OOP Refactor Object-Oriented Programming Refactor of Code label Jul 24, 2026

@michelebucelli michelebucelli left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @zasexton! I have left some comments. Some of them relate to the documentation, others to the interface itself.

In general, I am not sure I have understood correctly how the concrete rules are meant to be implemented (as you might see from some of my comments). In this respect, I think it would be useful to include in this PR at least one concrete rule (I assume you have implemented some, at least for the sake of testing the infrastructure), which would serve as a demonstration of how to use the abstract framework. I think this would make review a bit easier.

Comment on lines +12 to +15
COMMAND "${CMAKE_COMMAND}" -E remove_directory
"${SV_SOURCE_DIR}/../Documentation/build"
COMMAND "${DOXYGEN_EXECUTABLE}" "${SV_BINARY_DIR}/Doxyfile"
WORKING_DIRECTORY "${SV_SOURCE_DIR}/.."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this right, the purpose of this is to fix the make doc recipe (whereas as of now the documentation would have to be built by calling doxygen manually from the repository root folder). Is this correct?

If so, is the documentation now generated into the build folder, or into the source folder as in the current main branch?

Comment on lines +269 to 280
file(GLOB SOLVER_FE_QUADRATURE_SRCS CONFIGURE_DEPENDS
FE/Quadrature/*.cpp
FE/Quadrature/*.h
)

list(APPEND CSRCS
${SOLVER_CORE_SRCS}
${SOLVER_FE_COMMON_SRCS}
${SOLVER_FE_BASIS_SRCS}
${SOLVER_FE_MATH_SRCS}
${SOLVER_FE_QUADRATURE_SRCS}
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to have separate CMakeLists.txt files in the individual subfolders, and to build separate binaries that are then linked into the executable? The main CMakeLists.txt would then recurse into subfolders, instead of listing all the files.

In my opinion this would encourage the clean separation of the different modules, and it might be a good idea to start implementing it at least for the newly refactored parts of the library.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zasexton I agree with @michelebucelli; better to use CMake to manage sub-directories.

Comment on lines +12 to +14
* This header defines the consumer-facing representation of a finite-element
* quadrature rule. Rule construction and validation are implemented separately
* so consumers depend only on the stable query interface.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I would drop "finite-element" here, since the concept of a quadrature formula is independent of what one does with it (generic integrals or FE integrals).

Comment on lines +39 to +43
* The module does not choose the exactness required by an equation term, apply
* reduced-integration policy, select a basis, own mesh storage, embed or orient
* a face in its parent element, or map a reference integral into physical
* space. Those operations require solver, basis, mesh, and geometry context and
* remain at the caller's integration boundary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the same spirit as my previous comment about mentioning finite elements, I think this paragraph addresses concerns that are somewhat unnatural in the context of quadrature (or at least should be), and so I feel that it might risk creating confusion in the reader, rather than clarifying. I suggest rephrasing as follows:

This module is not concerned with selecting the quadrature rule based on the requirement of any specific application (e.g. on what functions must be integrated exactly). Furthermore, this module only deals with quadrature rules on reference cells, not on transformed cells. Those concerns are left to the consumers of this module.

More in general, I appreciate (a lot!) the level of detail in this documentation. However, in some places I feel that it uses jargon that is appropriate in a software engineering context but might be obscure to people without a structured software engineering background. Since I feel that many of the contributors to svmp might have a different background (e.g. applied mathematics or biomedical engineering), I think it might be beneficial to shift to a slightly more concrete and practical language. I will try to give some concrete suggestions in this direction in my other comments.

(The jargon issue might also be addressed by providing some sort of glossary, but I feel it might make things harder rather than easier to read).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zasexton I must admit that I am not very familiar with some of the terminology you use and have not ever seen them used in the CAD, FEA, robotics, etc. companies I've worked in. Like @michelebucelli says these terms will not be meaningful to the typical developer.

I am not quite on board with the first 100 lines of a file being comments; maybe this should be in a separate document.

I've seen criticism of code that needs extensive commenting; perhaps should be more expressive, clean and simple.

Comment on lines +47 to +54
* @ref svmp::FE::quadrature::QuadPoint "QuadPoint" and the const query surface
* of @ref svmp::FE::quadrature::QuadratureRule "QuadratureRule" form the public
* API. Integration consumers read rule metadata and ordered samples; they do
* not derive new rules or modify rule storage.
*
* Complete-data construction, reference-cell traits, point-containment checks,
* exact weight summation, concrete generators, caches, and rule-selection
* facilities are module implementation details.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the vein of my previous comment, I suggest rephrasing as follows (or similar):

The public API exposes the ordered weight-point pairs from the quadrature rule through the methods point, weight, points and weights of the QuadratureRule class. Additionally, a number of methods are exposed to access metadata about the rule such as the number of points or the degree of exactness. Other modules using quadrature formulas should exclusively rely on this information, and not on specific details of the concrete quadrature formulas being used.

(inserting Doxygen @refs appropriately so that the listed methods are linked back to their documentation).

Comment on lines +231 to +252
constexpr std::optional<ReferenceCellTraits> reference_cell_traits(
svmp::CellFamily family) noexcept
{
switch (family) {
case svmp::CellFamily::Point:
return ReferenceCellTraits{0, 1.0};
case svmp::CellFamily::Line:
return ReferenceCellTraits{1, 2.0};
case svmp::CellFamily::Triangle:
return ReferenceCellTraits{2, 0.5};
case svmp::CellFamily::Quad:
return ReferenceCellTraits{2, 4.0};
case svmp::CellFamily::Tetra:
return ReferenceCellTraits{3, 1.0 / 6.0};
case svmp::CellFamily::Hex:
return ReferenceCellTraits{3, 8.0};
case svmp::CellFamily::Wedge:
return ReferenceCellTraits{3, 1.0};
default:
return std::nullopt;
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any legitimate situation where this should be called against an unsupported reference cell? If not, I would suggest throwing an exception in the default case, and dropping the std::optional altogether.

Comment on lines +282 to +284
case svmp::CellFamily::Point:
is_contained = true;
break;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return true only if the provided point is coincident with the reference cell point (which I assume to be the origin [0, 0, 0])?

Comment on lines +357 to +359
if (polynomial_exactness < 0) {
return {"polynomial exactness must be non-negative"};
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this constraint be better represented by defining the polynomial exactness to be an unsigned int?

Comment on lines +387 to +388
message.append(" at sample ");
message.append(std::to_string(result.sample));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the meaning of "sample" in this context? Does it refer to the index of the quadrature point? In that case, I think making more explicit reference to the quadrature point index might be more easy to interpret, and slightly more in line with standard nomenclature.

Comment on lines +397 to +442
QuadratureRule::QuadratureRule(svmp::CellFamily family, RuleData data)
: QuadratureRule(validate(family, std::move(data)))
{
}

QuadratureRule::QuadratureRule(ValidatedState state)
: cell_family_(state.cell_family),
dimension_(state.dimension),
polynomial_exactness_(state.polynomial_exactness),
reference_cell_measure_(state.reference_cell_measure),
points_(std::move(state.points)),
weights_(std::move(state.weights))
{
}

QuadratureRule::ValidatedState QuadratureRule::validate(
svmp::CellFamily family,
RuleData data)
{
const auto traits = reference_cell_traits(family);
if (!traits) {
svmp::raise<InvalidArgumentException>(
validation_failure_message(
{"unsupported reference-cell family"}));
}

const auto validation = validate_rule_data(
family,
*traits,
data.polynomial_exactness,
data.points,
data.weights);
if (!validation.valid()) {
svmp::raise<InvalidArgumentException>(
validation_failure_message(validation));
}

return {
family,
traits->dimension,
data.polynomial_exactness,
traits->reference_cell_measure,
std::move(data.points),
std::move(data.weights),
};
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit more complicated than necessary to me. Wouldn't it be possible to merge all three methods (constructor from cell family and RuleData, constructor from ValidatedState and validate) into the first constructor only.

Unless there's some use cases that I am missing?

@zasexton

Copy link
Copy Markdown
Collaborator Author

Thanks for these comments @michelebucelli I'll review these today and make appropriate changes. Overall there is a good bit of important design feedback here that will help me to improve this base abstraction (which is the main purpose of me posting this draft PR). Thank you!

@ktbolt ktbolt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zasexton Why all of this complexity to implement just the few quadrature rules needed for the elements we support ?


constexpr double construction_validation_tolerance = 1.0e-12;
constexpr double coordinate_validation_tolerance = 1.0e-12;
constexpr double moment_validation_tolerance = 1.0e-12;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zasexton Where do these values come from ?

return ReferenceCellTraits{3, 8.0};
case svmp::CellFamily::Wedge:
return ReferenceCellTraits{3, 3, 1.0};
return ReferenceCellTraits{3, 1.0};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zasexton What do the float numbers mean ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OOP Refactor Object-Oriented Programming Refactor of Code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants