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.
- {torchode-0.1.8 → torchode-0.2.0}/CHANGELOG.md +12 -0
- {torchode-0.1.8 → torchode-0.2.0}/PKG-INFO +2 -6
- {torchode-0.1.8 → torchode-0.2.0}/README.md +0 -3
- torchode-0.2.0/docs/extra-stats.ipynb +271 -0
- torchode-0.2.0/docs/nd-data.md +34 -0
- {torchode-0.1.8 → torchode-0.2.0}/mkdocs.yml +2 -0
- {torchode-0.1.8 → torchode-0.2.0}/pyproject.toml +1 -4
- {torchode-0.1.8 → torchode-0.2.0}/torchode/__init__.py +1 -1
- {torchode-0.1.8 → torchode-0.2.0}/torchode/adjoints.py +43 -15
- {torchode-0.1.8 → torchode-0.2.0}/.github/workflows/python-package.yml +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/.gitignore +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/.readthedocs.yaml +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/LICENSE +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/README.md +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/comparison.md +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/extra-args.md +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/jit.ipynb +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/requirements.in +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/requirements.txt +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/step-size-controllers.md +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/docs/torchdiffeq.ipynb +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/interface.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/interpolation.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/problems.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/__init__.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/base.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/dopri5.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/euler.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/heun.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/runge_kutta.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/single_step_methods/tsit5.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/solution.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/status_codes.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/step_size_controllers.py +0 -0
- {torchode-0.1.8 → torchode-0.2.0}/torchode/terms.py +0 -0
- {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.
|
|
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 >=
|
|
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!
|
|
@@ -19,11 +19,8 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"sympy ~= 1.10",
|
|
22
|
-
"torch >=
|
|
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,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=
|
|
365
|
+
solution = solver.solve(problem, dt0=dt0, args=args, term=term)
|
|
365
366
|
|
|
366
367
|
ctx.stats = solution.stats
|
|
367
|
-
ctx.save_for_backward(
|
|
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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
436
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|