Skip to content

Object oriented refactoring of fcType#577

Open
michelebucelli wants to merge 19 commits into
SimVascular:mainfrom
michelebucelli:feature/oo-fctype
Open

Object oriented refactoring of fcType#577
michelebucelli wants to merge 19 commits into
SimVascular:mainfrom
michelebucelli:feature/oo-fctype

Conversation

@michelebucelli

@michelebucelli michelebucelli commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #575.

Current situation

The class fcType is currently defined in ComMod.h, and its interface and that of related function make its use slightly more cumbersome and less seamless than it needs to be (see discussion in #575 for more details).

Release Notes

  1. fcType renamed to FourierInterpolation, and its data members (now private) renamed with more explicit names.
  2. FourierInterpolation implementation moved to dedicated files fourier_interpolation.{h,cpp}.
  3. Construction of FourierInterpolation object is now done through static methods FourierInterpolation::from_time_series and FourierInterpolation::from_fourier_coefficients (while before it was done "manually" everywhere this was needed).
  4. Static methods FourierInterpolation::from_time_series_file and FourierInterpolation::from_fourier_coefficients_file manage reading data from file (while before it was done "manually" everywhere this was needed).
  5. Distribution of data in parallel is done with the (non-static) method FourierInterpolation::distribute.
  6. Evaluation of the interpolation is done through the methods FourierInterpolation::value and FourierInterpolation::value_and_derivative.
  7. Functions fft and ifft were tightly related to fcType (they worked on an fcType instance), and they have been merged into the methods FourierInterpolation class (for construction and evaluation, respectively).
  8. The rest of the code has been updated to adhere to this new interface.

All this should make it a bit easier to add support for reading time-dependent data from file in new classes (which is part of the aims of #519).

Documentation

The class and its methods have been given detailed Doxygen documentation.

Testing

The unit test and the integration tests all pass with the new interface.

TODO before marking as ready

  • expand suite of FFT-related unit tests

Code of Conduct & Contributing Guidelines

@codecov

codecov Bot commented Jun 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.14230% with 107 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.47%. Comparing base (9632608) to head (1bbbef1).

Files with missing lines Patch % Lines
Code/Source/solver/fourier_interpolation.cpp 74.91% 77 Missing ⚠️
...nitTests/math_tests/test_fourier_interpolation.cpp 90.47% 14 Missing ⚠️
Code/Source/solver/read_files.cpp 76.47% 8 Missing ⚠️
Code/Source/solver/Core/Exception.h 0.00% 6 Missing ⚠️
Code/Source/solver/set_bc.cpp 77.77% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #577      +/-   ##
==========================================
+ Coverage   69.44%   69.47%   +0.02%     
==========================================
  Files         181      183       +2     
  Lines       34072    34208     +136     
  Branches     5930     5932       +2     
==========================================
+ Hits        23662    23765     +103     
- Misses      10273    10306      +33     
  Partials      137      137              

☔ 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.

@michelebucelli michelebucelli marked this pull request as ready for review June 29, 2026 16:34

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Comment on lines 1 to 2
// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others.
// SPDX-License-Identifier: BSD-3-Clause

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In this PR, the functions fft and ifft have been moved from this file to fourier_interpolation.cpp, inside the new FourierInterpolation class. I think this makes sense, since those functions were tightly coupled to fcType.

However, this file also contains another function, igbc, which is now the only function left. The function is not related to Fourier transform, so it seems odd to keep the name of this file as it is.

I propose renaming it to igbc.cpp (and the header to igbc.h).

Comment on lines +525 to +530
// @todo[michelebucelli] This is consistent with the old code, but is
// incorrect when use_ramp = true. In that case, the derivative should be
// zero outside the interpolation interval [initial_time, initial_time +
// period].
if (evaluate_derivative)
derivative[i] = linear_trend_slopes[i];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This was probably an unintended behavior of the old code.

When using the "ramp" option, the following function is computed (the implementation is formulated slightly different):

$$ v(t) = \begin{cases} y_0 & \text{if } t < t_0 \\ y_0 + (y_{N-1} - y_{0}) \frac{t - t_0}{t_{N-1} - t_0} & \text{if } t_0 \leq t \leq t_{N-1} \\ y_{N-1} & \text{if } t > t_{N-1} \end{cases} $$

For times outside of the inerpolation interval $[t_0, t_{N-1}]$, I think the derivative should be set to zero:

Suggested change
// @todo[michelebucelli] This is consistent with the old code, but is
// incorrect when use_ramp = true. In that case, the derivative should be
// zero outside the interpolation interval [initial_time, initial_time +
// period].
if (evaluate_derivative)
derivative[i] = linear_trend_slopes[i];
if (evaluate_derivative) {
if (use_ramp && (time < initial_time || time > initial_time + period)) {
derivative[i] = 0.0;
} else {
derivative[i] = linear_trend_slopes[i];
}
}

I tried this out and all the tests still pass, so I propose changing this behavior.

Comment on lines +546 to +552
// @todo[michelebucelli] This seems pretty fragile! It happens in a few
// other places in this file as well, where using an increment
// operator (*= or +=) changes the order of operations resulting in
// changes in the test values. This should be investigated and the
// best (i.e. most accurate) operation order should be chosen.
value[j] = value[j] + fourier_coefficients_real(j, i) * std::cos(K) -
fourier_coefficients_imaginary(j, i) * std::sin(K);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This took me a while to figure out 😅 There's at least another place where the same thing happens. I'm honestly not sure which is the most correct option, but the fact that the order of operations makes a measurable difference probably means that this needs to be carefully evaluated.

Comment on lines +264 to +295
std::tie(value, derivative) = gt.value_and_derivative(x_start - 1.0);
ASSERT_NEAR(value[0], temporal_values[0][1], 1e-6)
<< "Expected value before initial time to be equal to initial value of "
"linear trend";

// Check that after the final time the value is equal to the final value of
// the linear trend.
std::tie(value, derivative) = gt.value_and_derivative(x_end + 1.0);
ASSERT_NEAR(value[0], temporal_values.back()[1], 1e-6)
<< "Expected value after final time to be equal to final value of linear "
"trend";

// Check that the value in the interpolation interval is a linear
// interpolation of the initial and final value.
for (double t = x_start; t <= x_end; t += 0.5) {
std::tie(value, derivative) = gt.value_and_derivative(t);

const double expected_y =
temporal_values[0][1] +
(temporal_values.back()[1] - temporal_values[0][1]) * (t - x_start) /
(x_end - x_start);
const double expected_z =
temporal_values[0][2] +
(temporal_values.back()[2] - temporal_values[0][2]) * (t - x_start) /
(x_end - x_start);

ASSERT_NEAR(value[0], expected_y, 1e-6)
<< "Expected value in interpolation interval to be linear "
"interpolation of initial and final value";
ASSERT_NEAR(value[1], expected_z, 1e-6)
<< "Expected value in interpolation interval to be linear "
"interpolation of initial and final value";

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These assertions should be extended to include checks on the evaluated derivative, once we agree whether to change or keep the current behavior as per my previous comment.

@michelebucelli michelebucelli added the OOP Refactor Object-Oriented Programming Refactor of Code label Jun 29, 2026
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.

Object oriented refactoring of fcType

1 participant