Skip to content

Commit ca8ec3a

Browse files
Update citation-handling to work with nested step size controllers
1 parent 5b962fb commit ca8ec3a

2 files changed

Lines changed: 68 additions & 16 deletions

File tree

diffrax/_autocitation.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -571,22 +571,21 @@ def _auto_dt0(dt0):
571571

572572

573573
@citation_rules.append
574-
def _clip_controller(terms, stepsize_controller):
575-
if type(stepsize_controller) is ClipStepSizeController:
576-
if stepsize_controller.store_rejected_steps is not None and is_sde(terms):
577-
return r"""
578-
% You are adaptively solving an SDE whilst revisiting rejected time points. This is a
579-
% subtle point required for the correctness of adaptive noncommutative SDE solves, as
580-
% found in:
581-
""" + _parse_reference(ClipStepSizeController)
574+
def _stepsize_controller(stepsize_controller, terms=None):
575+
out = _stepsize_controller_impl(terms, stepsize_controller)
576+
if len(out) == 0:
577+
return None
578+
else:
579+
return "\n\n".join(x.strip() for x in out)
582580

583581

584-
@citation_rules.append
585-
def _pid_controller(stepsize_controller, terms=None):
582+
def _stepsize_controller_impl(terms, stepsize_controller) -> set[str]:
583+
out = set()
586584
if type(stepsize_controller) is PIDController:
587585
if is_sde(terms):
588-
return r"""
589-
% The use of PI and PI controllers to adapt step sizes for SDEs are from:
586+
out.add(
587+
r"""
588+
% The use of adaptive step size controllers for SDEs are from:
590589
@article{burrage2004adaptive,
591590
title={Adaptive stepsize based on control theory for stochastic
592591
differential equations},
@@ -612,14 +611,16 @@ def _pid_controller(stepsize_controller, terms=None):
612611
pages={791–-812},
613612
}
614613
"""
614+
)
615615
else:
616616
no_p = stepsize_controller.pcoeff == 0
617617
no_d = stepsize_controller.dcoeff == 0
618618
_no_tracer(no_p, "stepsize_controller.pcoeff")
619619
_no_tracer(no_d, "stepsize_controller.dcoeff")
620620
if no_d:
621621
if no_p:
622-
return r"""
622+
out.add(
623+
r"""
623624
% The use of an I-controller to adapt step sizes is from Section II.4 of:
624625
@book{hairer2008solving-i,
625626
address={Berlin},
@@ -631,8 +632,10 @@ def _pid_controller(stepsize_controller, terms=None):
631632
year={2008}
632633
}
633634
"""
635+
)
634636
else:
635-
return r"""
637+
out.add(
638+
r"""
636639
% The use of a PI-controller to adapt step sizes is from Section IV.2 of:
637640
@book{hairer2002solving-ii,
638641
address={Berlin},
@@ -653,9 +656,11 @@ def _pid_controller(stepsize_controller, terms=None):
653656
pages={281--310}
654657
}
655658
"""
659+
)
656660
else:
657-
return r"""
658-
% The use of a PID controller to adapt step sizes is from:
661+
out.add(
662+
r"""
663+
% The use of a PID-controller to adapt step sizes is from:
659664
@article{soderlind2003digital,
660665
title={{D}igital {F}ilters in {A}daptive {T}ime-{S}tepping,
661666
author={Gustaf S{\"o}derlind},
@@ -666,3 +671,16 @@ def _pid_controller(stepsize_controller, terms=None):
666671
pages={1--26}
667672
}
668673
"""
674+
)
675+
elif type(stepsize_controller) is ClipStepSizeController:
676+
out.update(_stepsize_controller_impl(terms, stepsize_controller.controller))
677+
if stepsize_controller.store_rejected_steps is not None and is_sde(terms):
678+
out.add(
679+
r"""
680+
% You are adaptively solving an SDE whilst revisiting rejected time points. This is a
681+
% subtle point required for the correctness of adaptive noncommutative SDE solves, as
682+
% found in:
683+
"""
684+
+ _parse_reference(ClipStepSizeController)
685+
)
686+
return out

test/test_citation.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import diffrax as dfx
2+
3+
4+
def test_adaptive_sde(capfd, getkey):
5+
topline = "AUTOGENERATED REFERENCES PRODUCED USING `diffrax.citation(...)`"
6+
sde = "You are solving an SDE, and may wish to cite the textbook"
7+
adaptive = "The use of adaptive step size controllers for SDEs are from"
8+
reject_buffer = (
9+
"You are adaptively solving an SDE whilst revisiting rejected time points"
10+
)
11+
12+
bm = dfx.VirtualBrownianTree(0, 1, 1e-3, (), getkey())
13+
terms = dfx.ControlTerm(lambda t, y, args: -y, bm)
14+
stepsize_controller = dfx.PIDController(rtol=1e-3, atol=1e-3)
15+
capfd.readouterr()
16+
dfx.citation(terms=terms, stepsize_controller=stepsize_controller)
17+
out = capfd.readouterr().out
18+
assert topline in out
19+
assert sde in out
20+
assert adaptive in out
21+
assert reject_buffer not in out
22+
23+
capfd.readouterr()
24+
dfx.citation(
25+
terms=terms,
26+
stepsize_controller=dfx.ClipStepSizeController(
27+
stepsize_controller, store_rejected_steps=1
28+
),
29+
)
30+
out = capfd.readouterr().out
31+
assert topline in out
32+
assert sde in out
33+
assert adaptive in out
34+
assert reject_buffer in out

0 commit comments

Comments
 (0)