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 6 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 @@ class Stepsize:
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.
Can be a function of festim.t. Defaults to None.
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
milestones (list, optional): list of times by which the simulation must
pass. Defaults to None.


Attributes:
Expand All @@ -23,6 +30,9 @@ class Stepsize:
over (resp. under) which the stepsize is increased
(resp. decreased)
adaptive (bool): True if the stepsize is adaptive, False otherwise.
max_stepsize (float): Maximum stepsize.
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
milestones (list): list of times by which the simulation must
pass.
"""

def __init__(
Expand All @@ -31,14 +41,29 @@ def __init__(
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 @@ def cutback_factor(self, value):

self._cutback_factor = value

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

@max_stepsize.setter
def max_stepsize(self, value):
if value is not None and not callable(value):
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
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)
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

max_step = self.get_max_stepsize(t)

if nb_iterations < self.target_nb_iterations:
return value * self.growth_factor
updated_value = min(
value * self.growth_factor,
max_step if max_step is not None else 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

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(
t, next_milestone, atol=0
):
updated_value = time_to_milestone

return updated_value

def is_adapt(self, t):
"""
Expand All @@ -90,3 +158,20 @@ def is_adapt(self, t):
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
61 changes: 61 additions & 0 deletions test/test_stepsize.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,31 @@ def test_adaptive_stepsize_shrinks(cutback_factor, target):
expected_value = current_value * my_stepsize.cutback_factor
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
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved


def test_stepsize_is_unchanged():
"""
Expand Down Expand Up @@ -100,3 +125,39 @@ 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)])
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
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
kaelyndunnell marked this conversation as resolved.
Show resolved Hide resolved
Loading