torchode 0.1.8__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {torchode-0.1.8 → torchode-0.2.0}/CHANGELOG.md +12 -0
  2. {torchode-0.1.8 → torchode-0.2.0}/PKG-INFO +2 -6
  3. {torchode-0.1.8 → torchode-0.2.0}/README.md +0 -3
  4. torchode-0.2.0/docs/extra-stats.ipynb +271 -0
  5. torchode-0.2.0/docs/nd-data.md +34 -0
  6. {torchode-0.1.8 → torchode-0.2.0}/mkdocs.yml +2 -0
  7. {torchode-0.1.8 → torchode-0.2.0}/pyproject.toml +1 -4
  8. {torchode-0.1.8 → torchode-0.2.0}/torchode/__init__.py +1 -1
  9. {torchode-0.1.8 → torchode-0.2.0}/torchode/adjoints.py +43 -15
  10. {torchode-0.1.8 → torchode-0.2.0}/.github/workflows/python-package.yml +0 -0
  11. {torchode-0.1.8 → torchode-0.2.0}/.gitignore +0 -0
  12. {torchode-0.1.8 → torchode-0.2.0}/.readthedocs.yaml +0 -0
  13. {torchode-0.1.8 → torchode-0.2.0}/LICENSE +0 -0
  14. {torchode-0.1.8 → torchode-0.2.0}/docs/README.md +0 -0
  15. {torchode-0.1.8 → torchode-0.2.0}/docs/comparison.md +0 -0
  16. {torchode-0.1.8 → torchode-0.2.0}/docs/extra-args.md +0 -0
  17. {torchode-0.1.8 → torchode-0.2.0}/docs/jit.ipynb +0 -0
  18. {torchode-0.1.8 → torchode-0.2.0}/docs/requirements.in +0 -0
  19. {torchode-0.1.8 → torchode-0.2.0}/docs/requirements.txt +0 -0
  20. {torchode-0.1.8 → torchode-0.2.0}/docs/step-size-controllers.md +0 -0
  21. {torchode-0.1.8 → torchode-0.2.0}/docs/torchdiffeq.ipynb +0 -0
  22. {torchode-0.1.8 → torchode-0.2.0}/torchode/interface.py +0 -0
  23. {torchode-0.1.8 → torchode-0.2.0}/torchode/interpolation.py +0 -0
  24. {torchode-0.1.8 → torchode-0.2.0}/torchode/problems.py +0 -0
  25. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/__init__.py +0 -0
  26. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/base.py +0 -0
  27. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/dopri5.py +0 -0
  28. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/euler.py +0 -0
  29. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/heun.py +0 -0
  30. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/runge_kutta.py +0 -0
  31. {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/tsit5.py +0 -0
  32. {torchode-0.1.8 → torchode-0.2.0}/torchode/solution.py +0 -0
  33. {torchode-0.1.8 → torchode-0.2.0}/torchode/status_codes.py +0 -0
  34. {torchode-0.1.8 → torchode-0.2.0}/torchode/step_size_controllers.py +0 -0
  35. {torchode-0.1.8 → torchode-0.2.0}/torchode/terms.py +0 -0
  36. {torchode-0.1.8 → torchode-0.2.0}/torchode/typing.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.2.0 - 2023-11-10
9
+
10
+ ### Changed
11
+
12
+ - Removed compatibility with pytorch 1.x
13
+
14
+ ## 0.1.9 - 2023-08-29
15
+
16
+ ### Added
17
+
18
+ - Allow passing `dt_0` for the forward and backward pass in the backsolve adjoint
19
+
8
20
  ## 0.1.8 - 2023-03-25
9
21
 
10
22
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: torchode
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: A parallel ODE solver for PyTorch
5
5
  Author-email: Marten Lienen <m.lienen@tum.de>
6
6
  Requires-Python: >= 3.8
@@ -12,9 +12,8 @@ Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python
14
14
  Requires-Dist: sympy ~= 1.10
15
- Requires-Dist: torch >= 1.11
15
+ Requires-Dist: torch >= 2.0
16
16
  Requires-Dist: torchtyping ~= 0.1.4
17
- Requires-Dist: functorch
18
17
  Requires-Dist: pytest-benchmark ; extra == "benchmark"
19
18
  Requires-Dist: pytest ; extra == "test"
20
19
  Requires-Dist: numpy ~= 1.22 ; extra == "test"
@@ -77,9 +76,6 @@ step_size_controller = to.IntegralController(atol=1e-6, rtol=1e-3, term=term)
77
76
  solver = to.AutoDiffAdjoint(step_method, step_size_controller)
78
77
  jit_solver = torch.compile(solver)
79
78
 
80
- # For pytorch versions < 2.0, use the older TorchScript compiler
81
- #jit_solver = torch.jit.script(solver)
82
-
83
79
  sol = jit_solver.solve(to.InitialValueProblem(y0=y0, t_eval=t_eval))
84
80
  print(sol.stats)
85
81
  # => {'n_f_evals': tensor([26, 26]), 'n_steps': tensor([4, 2]),
@@ -53,9 +53,6 @@ step_size_controller = to.IntegralController(atol=1e-6, rtol=1e-3, term=term)
53
53
  solver = to.AutoDiffAdjoint(step_method, step_size_controller)
54
54
  jit_solver = torch.compile(solver)
55
55
 
56
- # For pytorch versions < 2.0, use the older TorchScript compiler
57
- #jit_solver = torch.jit.script(solver)
58
-
59
56
  sol = jit_solver.solve(to.InitialValueProblem(y0=y0, t_eval=t_eval))
60
57
  print(sol.stats)
61
58
  # => {'n_f_evals': tensor([26, 26]), 'n_steps': tensor([4, 2]),
@@ -0,0 +1,271 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "46327300-b000-4a93-b42a-7caef72e0a59",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Logging Solver Information"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "markdown",
13
+ "id": "45feb98e-1972-4600-a3a8-13cc1661a277",
14
+ "metadata": {},
15
+ "source": [
16
+ "One of the key features of torchode is that all components are replaceable and any components can log its own outputs (captured in a dictionary called `stats`). This means that you can inject your own code and log anything information that is relevant for your usecase. In this example, we will create a step size controller wrapper that logs the step times `t` and all accept decisions, i.e. if each step was accepted or rejected by the step size controller."
17
+ ]
18
+ },
19
+ {
20
+ "cell_type": "markdown",
21
+ "id": "5f35a722-6c14-49cb-9768-954e2ef5eb61",
22
+ "metadata": {},
23
+ "source": [
24
+ "We begin by importing relevant modules and defining a generic model class."
25
+ ]
26
+ },
27
+ {
28
+ "cell_type": "code",
29
+ "execution_count": 1,
30
+ "id": "c717d8d2-0564-424b-8b63-0bf74395c5fc",
31
+ "metadata": {},
32
+ "outputs": [],
33
+ "source": [
34
+ "import torch\n",
35
+ "import torch.nn as nn\n",
36
+ "import torchode as to\n",
37
+ "from torchode.step_size_controllers import StepSizeController\n",
38
+ "\n",
39
+ "torch.random.manual_seed(180819023);"
40
+ ]
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": 2,
45
+ "id": "98820150-ecf4-4ab4-88b7-fa1dcee681c5",
46
+ "metadata": {},
47
+ "outputs": [],
48
+ "source": [
49
+ "class Model(nn.Module):\n",
50
+ " def __init__(self, n_features, n_hidden):\n",
51
+ " super().__init__()\n",
52
+ " self.layers = nn.Sequential(\n",
53
+ " nn.Linear(n_features, n_hidden),\n",
54
+ " nn.Softplus(),\n",
55
+ " nn.Linear(n_hidden, n_hidden),\n",
56
+ " nn.Softplus(),\n",
57
+ " nn.Linear(n_hidden, n_features)\n",
58
+ " )\n",
59
+ " \n",
60
+ " def forward(self, t, y):\n",
61
+ " return self.layers(y)"
62
+ ]
63
+ },
64
+ {
65
+ "cell_type": "markdown",
66
+ "id": "9bd96be4-0a78-4e5f-9815-afda3953aa11",
67
+ "metadata": {},
68
+ "source": [
69
+ "Now we define the wrapper that will track the step size data. By deferring the actual functionality to another controller, we can re-use the existing controller implementations and focus on collecting the information that we care about, in this case the integration time points `t`, the step size `dt` and whether each step was accepted.\n",
70
+ "\n",
71
+ "To define this custom controller, we just have to satisfy the `StepSizeController` interface. For the actual functionality we defer to another controller instance. In `init` we additionally initialize fields in the statistics dictionary for the current solve to capture `t` and so on. The `adapt_step_size` method then records the information into those fields.\n",
72
+ "\n",
73
+ "Note that you could proceed in a similar way to track information about the stepping methods, e.g. dopri5, by defining a `SingleStepMethod`."
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": 3,
79
+ "id": "b8aed13a-a135-4ad7-aa37-f28a9837ded6",
80
+ "metadata": {},
81
+ "outputs": [],
82
+ "source": [
83
+ "class StepSizeControllerTracker(StepSizeController):\n",
84
+ " \"\"\"A wrapper that collects time step and step acceptance information.\"\"\"\n",
85
+ "\n",
86
+ " def __init__(self, controller: StepSizeController):\n",
87
+ " super().__init__()\n",
88
+ "\n",
89
+ " self.controller = controller\n",
90
+ "\n",
91
+ " def init(self, term, problem, method_order: int, dt0, *, stats, args):\n",
92
+ " stats[\"all_t\"] = []\n",
93
+ " stats[\"all_dt\"] = []\n",
94
+ " stats[\"all_accept\"] = []\n",
95
+ "\n",
96
+ " return self.controller.init(\n",
97
+ " term, problem, method_order, dt0, stats=stats, args=args\n",
98
+ " )\n",
99
+ "\n",
100
+ " def adapt_step_size(self, t0, dt, y0, step, state, stats):\n",
101
+ " accept, dt_next, state, status = self.controller.adapt_step_size(\n",
102
+ " t0, dt, y0, step, state, stats\n",
103
+ " )\n",
104
+ "\n",
105
+ " stats[\"all_t\"].append(t0)\n",
106
+ " stats[\"all_dt\"].append(dt)\n",
107
+ " stats[\"all_accept\"].append(accept)\n",
108
+ "\n",
109
+ " return accept, dt_next, state, status\n",
110
+ "\n",
111
+ " def merge_states(self, running, current, previous):\n",
112
+ " return self.controller.merge_states(running, current, previous)"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "markdown",
117
+ "id": "ebda2a83-81ee-48c8-93a4-dd6c8c73aa06",
118
+ "metadata": {},
119
+ "source": [
120
+ "Next, we construct a solver and wrap the step size controller with our tracker."
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "code",
125
+ "execution_count": 4,
126
+ "id": "3720e3bf-dc2a-4d91-a56f-b9ca93d77392",
127
+ "metadata": {},
128
+ "outputs": [],
129
+ "source": [
130
+ "n_features = 5\n",
131
+ "batch_size = 3\n",
132
+ "\n",
133
+ "model = Model(n_features=n_features, n_hidden=32)"
134
+ ]
135
+ },
136
+ {
137
+ "cell_type": "code",
138
+ "execution_count": 5,
139
+ "id": "d8a40685-5df1-4f56-b317-cd2e419cbee9",
140
+ "metadata": {},
141
+ "outputs": [],
142
+ "source": [
143
+ "dev = torch.device(\"cpu\")\n",
144
+ "term = to.ODETerm(model)\n",
145
+ "step_method = to.Dopri5(term=term)\n",
146
+ "step_size_controller = to.IntegralController(atol=1e-6, rtol=1e-3, term=term)\n",
147
+ "step_size_controller = StepSizeControllerTracker(step_size_controller)\n",
148
+ "adjoint = to.AutoDiffAdjoint(step_method, step_size_controller).to(dev)"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "markdown",
153
+ "id": "fe6e8b4e-331e-405a-8757-91d179e9da75",
154
+ "metadata": {},
155
+ "source": [
156
+ "Finally, we generate some example data and evaluate the ODE defined by a randomly initialized model."
157
+ ]
158
+ },
159
+ {
160
+ "cell_type": "code",
161
+ "execution_count": 6,
162
+ "id": "b6a004e0-4327-4d6f-b19e-fb44b21c9584",
163
+ "metadata": {},
164
+ "outputs": [],
165
+ "source": [
166
+ "t_eval = torch.tile(torch.linspace(0.0, 3.0, 10), (batch_size, 1))\n",
167
+ "problem = to.InitialValueProblem(y0=torch.zeros((batch_size, n_features)).to(dev), t_eval=t_eval.to(dev))\n",
168
+ "\n",
169
+ "sol = adjoint.solve(problem)"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "markdown",
174
+ "id": "dacffa2b-fed5-4a60-a41b-1c5027b2854f",
175
+ "metadata": {},
176
+ "source": [
177
+ "In the end, we can inspect the statistics recorded in the solution object and see that our custom step size controller collected the data."
178
+ ]
179
+ },
180
+ {
181
+ "cell_type": "code",
182
+ "execution_count": 7,
183
+ "id": "960abf1b-16c0-497d-bf89-ad999734039e",
184
+ "metadata": {},
185
+ "outputs": [
186
+ {
187
+ "name": "stdout",
188
+ "output_type": "stream",
189
+ "text": [
190
+ "tensor([[0.0000e+00, 0.0000e+00, 0.0000e+00],\n",
191
+ " [1.0000e-04, 1.0000e-04, 1.0000e-04],\n",
192
+ " [1.1000e-03, 1.1000e-03, 1.1000e-03],\n",
193
+ " [1.1100e-02, 1.1100e-02, 1.1100e-02],\n",
194
+ " [1.1110e-01, 1.1110e-01, 1.1110e-01],\n",
195
+ " [9.3798e-01, 9.3798e-01, 9.3798e-01]], grad_fn=<StackBackward0>)\n"
196
+ ]
197
+ }
198
+ ],
199
+ "source": [
200
+ "print(torch.stack(sol.stats[\"all_t\"]))"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "code",
205
+ "execution_count": 8,
206
+ "id": "6994ebb9-85e3-460f-8542-b0a00722c820",
207
+ "metadata": {},
208
+ "outputs": [
209
+ {
210
+ "name": "stdout",
211
+ "output_type": "stream",
212
+ "text": [
213
+ "tensor([[1.0000e-04, 1.0000e-04, 1.0000e-04],\n",
214
+ " [1.0000e-03, 1.0000e-03, 1.0000e-03],\n",
215
+ " [1.0000e-02, 1.0000e-02, 1.0000e-02],\n",
216
+ " [1.0000e-01, 1.0000e-01, 1.0000e-01],\n",
217
+ " [8.2688e-01, 8.2688e-01, 8.2688e-01],\n",
218
+ " [2.0620e+00, 2.0620e+00, 2.0620e+00]], grad_fn=<StackBackward0>)\n"
219
+ ]
220
+ }
221
+ ],
222
+ "source": [
223
+ "print(torch.stack(sol.stats[\"all_dt\"]))"
224
+ ]
225
+ },
226
+ {
227
+ "cell_type": "code",
228
+ "execution_count": 9,
229
+ "id": "7d19d776-d44e-4e8f-b370-2deceb8d1020",
230
+ "metadata": {},
231
+ "outputs": [
232
+ {
233
+ "name": "stdout",
234
+ "output_type": "stream",
235
+ "text": [
236
+ "tensor([[True, True, True],\n",
237
+ " [True, True, True],\n",
238
+ " [True, True, True],\n",
239
+ " [True, True, True],\n",
240
+ " [True, True, True],\n",
241
+ " [True, True, True]])\n"
242
+ ]
243
+ }
244
+ ],
245
+ "source": [
246
+ "print(torch.stack(sol.stats[\"all_accept\"]))"
247
+ ]
248
+ }
249
+ ],
250
+ "metadata": {
251
+ "kernelspec": {
252
+ "display_name": "Python 3 (ipykernel)",
253
+ "language": "python",
254
+ "name": "python3"
255
+ },
256
+ "language_info": {
257
+ "codemirror_mode": {
258
+ "name": "ipython",
259
+ "version": 3
260
+ },
261
+ "file_extension": ".py",
262
+ "mimetype": "text/x-python",
263
+ "name": "python",
264
+ "nbconvert_exporter": "python",
265
+ "pygments_lexer": "ipython3",
266
+ "version": "3.10.10"
267
+ }
268
+ },
269
+ "nbformat": 4,
270
+ "nbformat_minor": 5
271
+ }
@@ -0,0 +1,34 @@
1
+ # Images, Graphs and N-d Tensors
2
+
3
+ torchode is written with a sharp focus on its target problem: being a high-performance ODE
4
+ solver for PyTorch. To keep the code base as simple as possible, it neglects some niceties
5
+ such as arbitrary data dimensions. torchode expects your data to be exactly 2-dimensional,
6
+ `batch x features`. If your samples are not 1-dimensional vectors, but for example images,
7
+ you have to manage the shapes yourself. This means that you have to flatten the inputs
8
+ when you hand them over to torchode and reshape them, when you pass them to a
9
+ convolutional layer, let's say.
10
+
11
+ ```python
12
+ # Example with a batch of RGB images
13
+ b, c, w, h = 8, 3, 72, 72
14
+ y0 = torch.randn(b, w, h)
15
+
16
+ # Setup solver etc.
17
+ # ...
18
+
19
+ def f(t, y, args)
20
+ c, w, h = args
21
+
22
+ # Reshape 2D to B x C x W x H tensor and apply convolutional neural network
23
+ y = some_cnn(y.reshape((-1, c, w, h)))
24
+
25
+ # Flatten back to 2D when we return control back to torchode
26
+ return y.flatten(start_dim=1)
27
+
28
+ term = to.ODETerm(f, with_args=True)
29
+ problem = to.InitialValueProblem(y0=y0.flatten(start_dim=1), t_eval=..)
30
+ sol = solver.solve(problem, args=(c, w, h))
31
+ ```
32
+
33
+ If you are willing to contribute a small utility that does this shape flattening and
34
+ reshaping automatically, we would be happy about your contribution!
@@ -14,5 +14,7 @@ nav:
14
14
  - step-size-controllers.md
15
15
  - jit.ipynb
16
16
  - extra-args.md
17
+ - extra-stats.ipynb
18
+ - nd-data.md
17
19
  - torchdiffeq.ipynb
18
20
  - comparison.md
@@ -19,11 +19,8 @@ classifiers = [
19
19
  ]
20
20
  dependencies = [
21
21
  "sympy ~= 1.10",
22
- "torch >= 1.11",
22
+ "torch >= 2.0",
23
23
  "torchtyping ~= 0.1.4",
24
- # functorch has been integrated into pytorch and this is just a dummy dependency for
25
- # compatibility with pytorch <= 1.13
26
- "functorch",
27
24
  ]
28
25
 
29
26
  [project.optional-dependencies]
@@ -1,6 +1,6 @@
1
1
  """A parallel ODE solver for PyTorch"""
2
2
 
3
- __version__ = "0.1.8"
3
+ __version__ = "0.2.0"
4
4
 
5
5
  from .adjoints import AutoDiffAdjoint, BacksolveAdjoint, JointBacksolveAdjoint
6
6
  from .interface import register_method, solve_ivp
@@ -1,6 +1,5 @@
1
1
  from typing import Any, Dict, Optional
2
2
 
3
- import functorch
4
3
  import torch
5
4
  import torch.nn as nn
6
5
 
@@ -352,6 +351,8 @@ class BacksolveFunction(torch.autograd.Function):
352
351
  t_start,
353
352
  t_end,
354
353
  t_eval,
354
+ dt0,
355
+ backward_dt0,
355
356
  args,
356
357
  *adjoint_params,
357
358
  ):
@@ -361,10 +362,12 @@ class BacksolveFunction(torch.autograd.Function):
361
362
 
362
363
  problem = InitialValueProblem(y0, t_start, t_end, t_eval)
363
364
  with torch.no_grad():
364
- solution = solver.solve(problem, dt0=None, args=args, term=term)
365
+ solution = solver.solve(problem, dt0=dt0, args=args, term=term)
365
366
 
366
367
  ctx.stats = solution.stats
367
- ctx.save_for_backward(t_start, t_end, t_eval, solution.ys, *adjoint_params)
368
+ ctx.save_for_backward(
369
+ t_start, t_end, t_eval, backward_dt0, solution.ys, *adjoint_params
370
+ )
368
371
 
369
372
  return solution.ts, solution.ys, solution.stats, solution.status
370
373
 
@@ -373,7 +376,7 @@ class BacksolveFunction(torch.autograd.Function):
373
376
  aug_solver = ctx.aug_solver
374
377
  aug_term = ctx.aug_term
375
378
  args = ctx.args
376
- t_start, t_end, t_eval, ys, *adjoint_params = ctx.saved_tensors
379
+ t_start, t_end, t_eval, dt0, ys, *adjoint_params = ctx.saved_tensors
377
380
 
378
381
  stats = ctx.stats
379
382
  stats.setdefault("backsolve", [])
@@ -391,7 +394,7 @@ class BacksolveFunction(torch.autograd.Function):
391
394
  aug_state, t_start=t_end, t_end=t_start, t_eval=None
392
395
  )
393
396
  solution = aug_solver.solve(
394
- problem, dt0=None, args=(shapes, args), term=aug_term
397
+ problem, dt0=dt0, args=(shapes, args), term=aug_term
395
398
  )
396
399
  aug_state = solution.ys[:, -1]
397
400
 
@@ -405,7 +408,7 @@ class BacksolveFunction(torch.autograd.Function):
405
408
  t_eval=None,
406
409
  )
407
410
  solution = aug_solver.solve(
408
- problem, dt0=None, args=(shapes, args), term=aug_term
411
+ problem, dt0=dt0, args=(shapes, args), term=aug_term
409
412
  )
410
413
  aug_state = solution.ys[:, -1]
411
414
  aug_state[:, 1 : 1 + n_features] = ys[:, i - 1]
@@ -419,7 +422,20 @@ class BacksolveFunction(torch.autograd.Function):
419
422
  # Accumulate gradients over samples
420
423
  grad_params = [p.sum(dim=0) for p in grad_params]
421
424
 
422
- return None, None, None, None, grad_y0, None, None, None, None, *grad_params
425
+ return (
426
+ None,
427
+ None,
428
+ None,
429
+ None,
430
+ grad_y0,
431
+ None,
432
+ None,
433
+ None,
434
+ None,
435
+ None,
436
+ None,
437
+ *grad_params,
438
+ )
423
439
 
424
440
 
425
441
  class AugmentedDynamicsTerm(nn.Module):
@@ -432,22 +448,24 @@ class AugmentedDynamicsTerm(nn.Module):
432
448
  self.vmap_args_dims = vmap_args_dims
433
449
  self.vmap_randomness = vmap_randomness
434
450
 
435
- self.func, self.params, self.buffers = functorch.make_functional_with_buffers(
436
- self.term.f
437
- )
451
+ f = self.term.f
452
+ orig_params = dict(f.named_parameters())
453
+ buffers = dict(f.named_buffers())
454
+ params_names = orig_params.keys()
438
455
 
439
456
  def vjp_single_sample(t_i, y_i, adj_y_i, arg_i):
440
457
  def wrapper(params, t_, y_):
458
+ args = (t_, y_)
441
459
  if self.term.with_args:
442
- return self.func(params, self.buffers, t_, y_, arg_i)
443
- else:
444
- return self.func(params, self.buffers, t_, y_)
460
+ args = args + (arg_i,)
461
+ params_dict = {name: value for name, value in zip(params_names, params)}
462
+ return torch.func.functional_call(f, (params_dict, buffers), args)
445
463
 
446
- dy, vjp = functorch.vjp(wrapper, self.params, t_i, y_i)
464
+ dy, vjp = torch.func.vjp(wrapper, tuple(orig_params.values()), t_i, y_i)
447
465
  vjp_params, vjp_t, vjp_y = vjp(-adj_y_i)
448
466
  return dy, vjp_t, vjp_y, vjp_params
449
467
 
450
- self.vjp_vf = functorch.vmap(
468
+ self.vjp_vf = torch.func.vmap(
451
469
  vjp_single_sample,
452
470
  in_dims=(0, 0, 0, self.vmap_args_dims),
453
471
  randomness=self.vmap_randomness,
@@ -491,7 +509,10 @@ class BacksolveAdjoint(nn.Module):
491
509
  term: Optional[ODETerm] = None,
492
510
  dt0: Optional[TimeTensor] = None,
493
511
  args: Any = None,
512
+ backward_dt0: Optional[TimeTensor] = None,
494
513
  ) -> Solution:
514
+ if backward_dt0 is None and dt0 is not None:
515
+ backward_dt0 = -dt0
495
516
  ts, ys, stats, status = BacksolveFunction.apply(
496
517
  self.forward_adjoint,
497
518
  self.backward_adjoint,
@@ -501,6 +522,8 @@ class BacksolveAdjoint(nn.Module):
501
522
  problem.t_start,
502
523
  problem.t_end,
503
524
  problem.t_eval,
525
+ dt0,
526
+ backward_dt0,
504
527
  args,
505
528
  *list(self.term.parameters()),
506
529
  )
@@ -601,6 +624,7 @@ class JointBacksolveAdjoint(nn.Module):
601
624
  term: Optional[ODETerm] = None,
602
625
  dt0: Optional[TimeTensor] = None,
603
626
  args: Any = None,
627
+ backward_dt0: Optional[TimeTensor] = None,
604
628
  ) -> Solution:
605
629
  y0 = problem.y0.flatten()[None]
606
630
  t_start = problem.t_start[:1]
@@ -623,6 +647,8 @@ class JointBacksolveAdjoint(nn.Module):
623
647
  t_slope = all_slopes / all_slopes[0]
624
648
  t_intercept = problem.t_start - t_slope * problem.t_start[0]
625
649
 
650
+ if backward_dt0 is None and dt0 is not None:
651
+ backward_dt0 = -dt0
626
652
  _, ys, stats, status = BacksolveFunction.apply(
627
653
  self.forward_loop,
628
654
  self.backward_loop,
@@ -632,6 +658,8 @@ class JointBacksolveAdjoint(nn.Module):
632
658
  t_start,
633
659
  t_end,
634
660
  t_eval,
661
+ dt0,
662
+ backward_dt0,
635
663
  (problem.batch_size, t_intercept, t_slope, args),
636
664
  *list(self.term.parameters()),
637
665
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes