Skip to content

Expose export time tolerances in Settings - #1290

Open
steps-re wants to merge 2 commits into
festim-dev:mainfrom
steps-re:fix/export-time-tolerance-v3
Open

steps-re wants to merge 2 commits into
festim-dev:mainfrom
steps-re:fix/export-time-tolerance-v3

Conversation

@steps-re

@steps-re steps-re commented Nov 4, 2026

Copy link
Copy Markdown

Description

Summary

Adds export_time_atol (default 0) and export_time_rtol (default 1e-5) to F.Settings, and passes them to is_it_time_to_export from the three post-processing loops. Defaults are unchanged, so existing runs behave as before.

Related Issues

Fixes #1074

Motivation and Context

is_it_time_to_export matches with np.isclose(time, current_time, atol=0, rtol=1e-5), so the window grows with time. At t = 0.9e8 it is 900 s wide. In the issue's example the export fires at t = 89999991, nine seconds early, and since the matched time is consumed, nothing is written at 0.9e8 itself.

This follows the solution proposed in the issue: one pair of tolerances on Settings, used by all exports. Negative values raise a ValueError, as Stepsize.milestone_tolerance does, since a negative tolerance would silently disable every timed export.

The loops now check getattr(export, "times", None) is not None before reading the settings, so problems built without Settings (as some unit tests do) keep working. Behaviour is otherwise identical: is_it_time_to_export already returned True for times=None.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

Testing

  • All existing tests pass locally (pytest)
  • I have added new tests that prove my fix is effective or that my feature works

test_profile_export_time_at_large_time is the issue's example with export_time_atol=1e-6, export_time_rtol=0. It checks the export lands exactly on 0.9e8 and writes once. test_settings.py covers the defaults, storing the values, and the ValueError for negatives.

Against current main without the change, 5 of the new tests fail (4 in test_settings.py, plus the profile test). With it, all pass.

Full suite on dolfinx 0.11.0 (macOS arm64), rebased on current main: 1265 passed, 5 skipped, 0 failed. ruff format --check and ruff check are clean on the touched files.

Code Quality Checklist

  • My code follows the code style of this project (Ruff formatted: ruff format .)
  • My code passes linting checks (ruff check .) (on the touched files)
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas

Documentation

  • I have updated the documentation accordingly (if applicable). New entries in userguide/settings.rst, and a paragraph under "Exporting at chosen times" in export_post_processing.rst with the large-time example.
  • I have added docstrings to new functions/classes following the project conventions

Additional Notes

With an adaptive stepsize, the solver also has to land on the requested time, so the docs point to Stepsize.milestone_tolerance too. I left the defaults alone to avoid changing existing outputs. Happy to switch the default to an absolute tolerance if you'd rather.

AI disclosure: I used an AI coding assistant (Claude) to help write and test this. I reviewed the change and ran the tests before and after the fix myself.

is_it_time_to_export compared the current time to each requested export
time with np.isclose(atol=0, rtol=1e-5), and the post_processing loops
never passed anything else. The window is relative to the current time,
so at t = 0.9e8 it is 900 s wide: the export in the issue's example fired
at t = 89999991, nine seconds early, and the matched time was consumed so
nothing was written at 0.9e8 itself.

Add export_time_atol (default 0) and export_time_rtol (default 1e-5) to
F.Settings and pass them from the three post_processing loops. Defaults
are unchanged, so existing runs behave as before. Negative values raise
a ValueError, as Stepsize.milestone_tolerance does, since a negative
tolerance would silently disable every timed export. The loops only read
the settings when an export actually has times, so problems built
without Settings (as some unit tests do) keep working.

Fixes festim-dev#1074
@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.12%. Comparing base (1bd7a80) to head (fd81744).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1290      +/-   ##
==========================================
- Coverage   96.14%   96.12%   -0.03%     
==========================================
  Files          57       57              
  Lines        5165     5183      +18     
==========================================
+ Hits         4966     4982      +16     
- Misses        199      201       +2     
Flag Coverage Δ
dolfinx-v0.10.0 93.24% <100.00%> (-0.02%) ⬇️
dolfinx-v0.11.0 95.98% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@RemDelaporteMathurin RemDelaporteMathurin 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.

Thanks @steps-re for this! This looks good to me, I had a couple of comments

* ``final_time``: the final time of a transient simulation
* ``stepsize``: the stepsize of a transient simulation, as a :class:`festim.Stepsize` or a number (see :ref:`Stepsize <stepsize_guide>`)
* ``element_degree``: the degree of the finite elements used for the concentrations (default: 1)
* ``export_time_atol`` and ``export_time_rtol``: the absolute and relative tolerances used to decide whether the current time matches one of the ``times`` requested by an export (default: ``0`` and ``1e-5``, see :ref:`Exporting at chosen times`)

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.

Broader discussion we're having with @jhdark is this Settings docs page since this is basically a duplicate of the API-reference... probably not needed!

Comment thread src/festim/settings.py
Comment on lines +22 to +29
export_time_atol (float, optional): Absolute tolerance used to decide
whether the current time matches one of the ``times`` requested by
an export. Defaults to 0.
export_time_rtol (float, optional): Relative tolerance (with respect to
the current time) used to decide whether the current time matches
one of the ``times`` requested by an export. At large times the
relative tolerance alone can be very wide, and ``export_time_atol``
should be used instead. Defaults to 1e-5.

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.

Very nice!

Comment thread test/test_profile.py
my_model.show_progress_bar = False
my_model.run()

assert my_model.exports[0].t == [export_time]

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.

Curiousity question as I'm not entirely sure: when we write an export, what time to we add to .t: is it the "real time" (eg. 0.9e8 +- epsilon) or the "target time" (here exactly 0.9e8)?

@RemDelaporteMathurin RemDelaporteMathurin added the enhancement New feature or request label Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose rtol and atol for is_it_time_to_export

2 participants