Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adaptive timestepping #916

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0344d53
adding max_stepsize attribute to stepsize class
kaelyndunnell Nov 5, 2024
f0432e6
added tests for max_stepsize & max_stepsize_setter
kaelyndunnell Nov 5, 2024
41e34fd
make max_stepsize accept functions of time
kaelyndunnell Nov 5, 2024
29b4d6e
added milestones
kaelyndunnell Nov 5, 2024
b45f6a0
removed t argument from property
kaelyndunnell Nov 5, 2024
d06f9a4
Black formatted, adding milestone tests
kaelyndunnell Nov 5, 2024
b324e29
black formatted
kaelyndunnell Nov 5, 2024
5d36191
Update src/festim/stepsize.py
kaelyndunnell Nov 5, 2024
7b67ba2
Update src/festim/stepsize.py
kaelyndunnell Nov 5, 2024
dbb7994
Update src/festim/stepsize.py
kaelyndunnell Nov 5, 2024
1e57e01
fix max stepsize check in modify_value & add test (thanks @RemDelapor…
kaelyndunnell Nov 5, 2024
c5c7760
Update src/festim/stepsize.py
kaelyndunnell Nov 5, 2024
157f50b
refactored test
RemDelaporteMathurin Nov 5, 2024
cc0e3c5
added test_milestones edge cases
kaelyndunnell Nov 5, 2024
accd037
remove unnecessary test
kaelyndunnell Nov 5, 2024
0d23289
black formatted
kaelyndunnell Nov 5, 2024
f8bc69d
added get_max_stepsize test
kaelyndunnell Nov 5, 2024
e1d12af
black formatted
kaelyndunnell Nov 5, 2024
00441d8
fixed expected_values in test_next_milestone
kaelyndunnell Nov 5, 2024
cbcdc4d
add overshoot_milestone test
kaelyndunnell Nov 5, 2024
9cabbf4
docstring typo
kaelyndunnell Nov 5, 2024
798d51e
fixed overshoot_milestone test
kaelyndunnell Nov 5, 2024
5a03a55
test typo
kaelyndunnell Nov 5, 2024
177f9d0
fix test
kaelyndunnell Nov 5, 2024
0d171b5
fix testing error
kaelyndunnell Nov 6, 2024
ae68cf2
test bug
kaelyndunnell Nov 6, 2024
d1f5267
fixed bug caught by CI
kaelyndunnell Nov 6, 2024
30763ab
adjusted code
kaelyndunnell Nov 6, 2024
e8bd6dd
fix time argument in modify_value
kaelyndunnell Nov 6, 2024
436bb60
black formatted
kaelyndunnell Nov 6, 2024
7fd4921
fix t argument in modify_value
kaelyndunnell Nov 6, 2024
b154cd0
updating test values
kaelyndunnell Nov 6, 2024
6b68b91
updating test params
kaelyndunnell Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 88 additions & 3 deletions src/festim/stepsize.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import numpy as np


class Stepsize:
"""
A class for evaluating the stepsize of transient simulations.
Expand All @@ -11,6 +14,10 @@
target_nb_iterations (int, optional): number of Newton iterations
over (resp. under) which the stepsize is increased
(resp. decreased)
max_stepsize (float or callable, optional): Maximum stepsize.
If callable, has to be a function of `t`. Defaults to None.
milestones (list, optional): list of times by which the simulation must
pass. Defaults to None.


Attributes:
Expand All @@ -23,6 +30,9 @@
over (resp. under) which the stepsize is increased
(resp. decreased)
adaptive (bool): True if the stepsize is adaptive, False otherwise.
max_stepsize (float, callable): Maximum stepsize.
milestones (list): list of times by which the simulation must
pass.
"""

def __init__(
Expand All @@ -31,14 +41,29 @@
growth_factor=None,
cutback_factor=None,
target_nb_iterations=None,
max_stepsize=None,
milestones=None,
) -> None:
self.initial_value = initial_value
self.growth_factor = growth_factor
self.cutback_factor = cutback_factor
self.target_nb_iterations = target_nb_iterations
self.max_stepsize = max_stepsize
self.milestones = milestones

# TODO should this class hold the dt object used in the formulation

@property
def milestones(self):
return self._milestones

@milestones.setter
def milestones(self, value):
if value:
self._milestones = sorted(value)
else:
self._milestones = value

@property
def adaptive(self):
return self.growth_factor or self.cutback_factor or self.target_nb_iterations
Expand Down Expand Up @@ -67,16 +92,59 @@

self._cutback_factor = value

@property
def max_stepsize(self):
return self._max_stepsize

@max_stepsize.setter
def max_stepsize(self, value):
if isinstance(value, float):
if value < self.initial_value:
raise ValueError(
"maximum stepsize cannot be less than initial stepsize"
)

self._max_stepsize = value

def get_max_stepsize(self, t):
"""
Returns the maximum stepsize at time t.

Args:
t (float): the current time

Returns:
float or None: the maximum stepsize at time t
"""
if callable(self._max_stepsize):
return self._max_stepsize(t)

Check warning on line 120 in src/festim/stepsize.py

View check run for this annotation

Codecov / codecov/patch

src/festim/stepsize.py#L120

Added line #L120 was not covered by tests
return self._max_stepsize

def modify_value(self, value, nb_iterations, t=None):
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
if not self.is_adapt(t):
return value

if nb_iterations < self.target_nb_iterations:
return value * self.growth_factor
updated_value = value * self.growth_factor
elif nb_iterations > self.target_nb_iterations:
return value * self.cutback_factor
updated_value = value * self.cutback_factor
else:
return value
updated_value = value

max_step = self.get_max_stepsize(t)
if max_step:
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
if updated_value > max_step:
updated_value = max_step

next_milestone = self.next_milestone(t)
if next_milestone is not None:
time_to_milestone = next_milestone - t
if updated_value > time_to_milestone and not np.isclose(

Check warning on line 142 in src/festim/stepsize.py

View check run for this annotation

Codecov / codecov/patch

src/festim/stepsize.py#L141-L142

Added lines #L141 - L142 were not covered by tests
t, next_milestone, atol=0
):
updated_value = time_to_milestone

Check warning on line 145 in src/festim/stepsize.py

View check run for this annotation

Codecov / codecov/patch

src/festim/stepsize.py#L145

Added line #L145 was not covered by tests

return updated_value

def is_adapt(self, t):
"""
Expand All @@ -90,3 +158,20 @@
bool: True if needs to adapt, False otherwise.
"""
return True

def next_milestone(self, current_time: float):
"""Returns the next milestone that the simulation must pass.
Returns None if there are no more milestones.

Args:
current_time (float): current time.

Returns:
float: next milestone.
"""
if self.milestones is None:
return None
for milestone in self.milestones:
if current_time < milestone:
return milestone
return None
89 changes: 89 additions & 0 deletions test/test_stepsize.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ def test_adaptive_stepsize_shrinks(cutback_factor, target):
assert np.isclose(new_value, expected_value)


@pytest.mark.parametrize("max_stepsize, growth_factor, target", [(4, 3, 1)])
def test_max_stepsize(max_stepsize, growth_factor, target):
"""Checks that the stepsize is capped at max
stepsize.

Args:
max_stepsize (float): maximum stepsize
growth_factor (float): the growth factor
target (int): the target number of iterations
"""

my_stepsize = F.Stepsize(initial_value=2)
my_stepsize.max_stepsize = max_stepsize
my_stepsize.growth_factor = growth_factor
my_stepsize.target_nb_iterations = target

current_value = 2
new_value = my_stepsize.modify_value(
value=current_value,
nb_iterations=my_stepsize.target_nb_iterations - 1,
)

expected_value = max_stepsize
assert new_value == expected_value


def test_stepsize_is_unchanged():
"""
Checks that the stepsize is unchanged when reaches
Expand Down Expand Up @@ -100,3 +126,66 @@ def test_cutback_factor_setter():
# Test that setting cutback factor to None works
stepsize.cutback_factor = None
assert stepsize.cutback_factor is None


def test_max_stepsize_setter():
"""Checks that the maximum stepsize setter works correctly"""
stepsize = F.Stepsize(initial_value=1)

# Test that setting a maximum stepsize less than initial stepsize raises a ValueError
with pytest.raises(
ValueError, match="maximum stepsize cannot be less than initial stepsize"
):
stepsize.max_stepsize = 0.5

# Test that setting maximum stepsize to None works
stepsize.max_stepsize = None
assert stepsize.max_stepsize is None


@pytest.mark.parametrize(
"milestones, current_time, expected_value",
[([1.0, 25.4], 20.1, 25.4), ([9.8], 10.0, None)],
)
def test_next_milestone(milestones, current_time, expected_value):
"""Checks that the next milestone is
identified and set correctly.

Args:
milestone (float): next milestone
current_time (float): current time in simulation
"""
stepsize = F.Stepsize(initial_value=0.5)

stepsize.milestones = milestones

next_milestone = stepsize.next_milestone(current_time=current_time)
assert expected_value == next_milestone


def test_no_milestones():
"""Checks that no milestones works correctly"""
stepsize = F.Stepsize(initial_value=0.5)

# Test that setting milestones to None works
stepsize.milestones = None
assert stepsize.milestones is None


def test_modify_for_stepsize():
"""Tests that modify_value returns max_stepsize
when max_stepsize is less than next stepsize.
"""
my_stepsize = F.Stepsize(initial_value=1)
my_stepsize.max_stepsize = 1
my_stepsize.growth_factor = 1.1
my_stepsize.target_nb_iterations = 4

current_value = 2
new_value = my_stepsize.modify_value(
value=current_value,
nb_iterations=my_stepsize.target_nb_iterations,
)

expected_value = 1
assert new_value == expected_value
Loading