diffct 2.3.0__py3-none-any.whl → 2.4.1__py3-none-any.whl
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.
- diffct/__init__.py +5 -1
- diffct/_solver_state.py +49 -26
- diffct/backends.py +289 -144
- diffct/chunking.py +34 -0
- diffct/distribution.py +67 -7
- diffct/geometry.py +93 -17
- diffct/kernels/cone_beam.py +48 -28
- diffct/kernels/fan_beam.py +23 -16
- diffct/kernels/geometry_vjp.py +45 -31
- diffct/kernels/surface_geometry.py +130 -0
- diffct/operators.py +806 -347
- diffct/projectors.py +62 -30
- diffct/solvers.py +18 -11
- diffct/surface_gradients.py +117 -0
- diffct/surfaces.py +86 -43
- {diffct-2.3.0.dist-info → diffct-2.4.1.dist-info}/METADATA +24 -18
- diffct-2.4.1.dist-info/RECORD +33 -0
- diffct-2.3.0.dist-info/RECORD +0 -31
- {diffct-2.3.0.dist-info → diffct-2.4.1.dist-info}/WHEEL +0 -0
- {diffct-2.3.0.dist-info → diffct-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {diffct-2.3.0.dist-info → diffct-2.4.1.dist-info}/licenses/NOTICE +0 -0
diffct/__init__.py
CHANGED
|
@@ -30,6 +30,8 @@ from .reconstruction import (
|
|
|
30
30
|
from .solvers import SolverResult, cgls, sirt, tv_reconstruction, cgls_into, total_variation
|
|
31
31
|
|
|
32
32
|
from .geometry import (
|
|
33
|
+
transform_trajectory,
|
|
34
|
+
circular_trajectory_3d_parallel,
|
|
33
35
|
circular_trajectory_3d,
|
|
34
36
|
random_trajectory_3d,
|
|
35
37
|
spiral_trajectory_3d,
|
|
@@ -62,7 +64,7 @@ try:
|
|
|
62
64
|
except ImportError:
|
|
63
65
|
pass
|
|
64
66
|
|
|
65
|
-
__version__ = '2.
|
|
67
|
+
__version__ = '2.4.1'
|
|
66
68
|
|
|
67
69
|
__all__ = [
|
|
68
70
|
'Projector',
|
|
@@ -88,6 +90,8 @@ __all__ = [
|
|
|
88
90
|
'FanBackprojectorFunction',
|
|
89
91
|
'ConeProjectorFunction',
|
|
90
92
|
'ConeBackprojectorFunction',
|
|
93
|
+
'transform_trajectory',
|
|
94
|
+
'circular_trajectory_3d_parallel',
|
|
91
95
|
'circular_trajectory_3d',
|
|
92
96
|
'random_trajectory_3d',
|
|
93
97
|
'spiral_trajectory_3d',
|
diffct/_solver_state.py
CHANGED
|
@@ -11,6 +11,7 @@ import torch.distributed as dist
|
|
|
11
11
|
|
|
12
12
|
from .pipeline import _fragments
|
|
13
13
|
from .storage import NpyStore, TensorStore
|
|
14
|
+
from .distribution import control_status, preflight
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def count(value, name, positive=False):
|
|
@@ -25,47 +26,56 @@ def finite(value, name, positive=False):
|
|
|
25
26
|
return float(value)
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
def agreement(operator, error=None, token=None):
|
|
29
|
+
def agreement(operator, error=None, token=None, *, cache_key=None):
|
|
29
30
|
"""Every rank rejects a bad request before numerical execution starts."""
|
|
30
31
|
world = getattr(operator, "world_size", 1)
|
|
31
32
|
if world == 1:
|
|
32
33
|
if error is not None:
|
|
33
34
|
raise error
|
|
34
35
|
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
error = None if error is None else ValueError("distributed solver agreement failed: " + str(error))
|
|
37
|
+
mismatch = "distributed solver agreement failed: inconsistent checkpoint iteration or controls"
|
|
38
|
+
partition = getattr(operator, "_partition", None)
|
|
39
|
+
if partition is not None:
|
|
40
|
+
partition.preflight(error, token, cache_key=cache_key, mismatch=mismatch)
|
|
41
|
+
else:
|
|
42
|
+
preflight(error, token, process_group=operator.process_group, world_size=world,
|
|
43
|
+
mismatch=mismatch)
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
def
|
|
44
|
-
"""
|
|
45
|
-
world = getattr(operator, "world_size", 1)
|
|
46
|
+
def owns_values(operator, kind):
|
|
47
|
+
"""Select one copy of each physical ray or voxel for scalar reductions."""
|
|
46
48
|
partition = getattr(operator, "partition", "views")
|
|
47
49
|
rank = getattr(operator, "rank", 0)
|
|
48
|
-
|
|
50
|
+
return (getattr(operator, "view_rank", 0) == 0 if kind == "volume"
|
|
49
51
|
else getattr(operator, "space_rank", 0) == 0) if partition == "hybrid" else (
|
|
50
52
|
rank == 0 if (kind == "volume") == (partition == "views") else True
|
|
51
53
|
)
|
|
52
|
-
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def scalar_sum(operator, value, kind):
|
|
57
|
+
"""Count physical rays/voxels once, including view/space replicas."""
|
|
58
|
+
world = getattr(operator, "world_size", 1)
|
|
59
|
+
value = value if owns_values(operator, kind) else 0.
|
|
53
60
|
if world > 1:
|
|
54
61
|
group = operator.process_group
|
|
55
62
|
device = torch.device("cuda", torch.cuda.current_device()) if dist.get_backend(group) == "nccl" else torch.device("cpu")
|
|
56
|
-
accumulator = torch.
|
|
63
|
+
accumulator = torch.as_tensor(value, dtype=torch.float64, device=device).clone()
|
|
57
64
|
dist.all_reduce(accumulator, group=group)
|
|
58
|
-
|
|
59
|
-
return value
|
|
65
|
+
return accumulator.item()
|
|
66
|
+
return float(value)
|
|
60
67
|
|
|
61
68
|
|
|
62
69
|
def squared_norm(operator, value, kind, elements=1048576):
|
|
63
70
|
store = TensorStore(value) if isinstance(value, torch.Tensor) else value
|
|
64
71
|
total = 0.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
if owns_values(operator, kind):
|
|
73
|
+
total = torch.zeros((), dtype=torch.float64, device=getattr(store, "device", "cpu"))
|
|
74
|
+
for selected in _fragments(tuple(store.shape), elements):
|
|
75
|
+
# Clone even an FP64 view: norms must never alter caller measurements.
|
|
76
|
+
block = store.read(selected).to(dtype=torch.float64).clone()
|
|
77
|
+
# Preserve the block reductions and their sequential FP64 order.
|
|
78
|
+
total.add_(block.square_().sum().to(device=total.device))
|
|
69
79
|
return scalar_sum(operator, total, kind)
|
|
70
80
|
|
|
71
81
|
|
|
@@ -201,6 +211,8 @@ def load_checkpoint(operator, directory, expected, iterations, elements, names):
|
|
|
201
211
|
try:
|
|
202
212
|
directory = Path(directory).resolve()
|
|
203
213
|
saved = json.loads((directory / "metadata.json").read_text(encoding="utf-8"))
|
|
214
|
+
if not isinstance(saved, dict):
|
|
215
|
+
raise ValueError("checkpoint metadata must be an object")
|
|
204
216
|
# JSON canonicalization also normalizes tuple-valued physical spacing.
|
|
205
217
|
canonical = json.loads(json.dumps(expected))
|
|
206
218
|
if any(saved.get(key) != value for key, value in canonical.items()):
|
|
@@ -215,12 +227,16 @@ def load_checkpoint(operator, directory, expected, iterations, elements, names):
|
|
|
215
227
|
if expected["algorithm"] in ("cgls", "cgls_into"):
|
|
216
228
|
finite(saved.get("gamma"), "checkpoint gamma")
|
|
217
229
|
manifest = saved.get("states", {})
|
|
230
|
+
if not isinstance(manifest, dict):
|
|
231
|
+
raise ValueError("checkpoint states must be an object")
|
|
218
232
|
if set(manifest) != set(names):
|
|
219
233
|
raise ValueError("checkpoint does not contain the complete solver state")
|
|
220
234
|
states = {}
|
|
221
235
|
for name in names:
|
|
222
|
-
store = NpyStore(directory / f"{name}.npy", mode="r")
|
|
223
236
|
specification = manifest[name]
|
|
237
|
+
if not isinstance(specification, dict):
|
|
238
|
+
raise ValueError("checkpoint state specification must be an object")
|
|
239
|
+
store = NpyStore(directory / f"{name}.npy", mode="r")
|
|
224
240
|
shape = operator.projection_shape if name in ("r", "q", "row") else operator.volume_shape
|
|
225
241
|
if name == "step":
|
|
226
242
|
shape = (1,)
|
|
@@ -270,15 +286,22 @@ def save_checkpoint(operator, directory, states, metadata, iteration, history, m
|
|
|
270
286
|
agreement(operator, error, iteration)
|
|
271
287
|
|
|
272
288
|
|
|
289
|
+
def callbacks_enabled(operator, callback):
|
|
290
|
+
"""Check callback presence once, including callbacks on only some ranks."""
|
|
291
|
+
return bool(control_status((int(callback is not None),),
|
|
292
|
+
process_group=getattr(operator, "process_group", None),
|
|
293
|
+
world_size=getattr(operator, "world_size", 1))[0])
|
|
294
|
+
|
|
295
|
+
|
|
273
296
|
def callback_stop(operator, callback, iteration, residual, volume):
|
|
274
297
|
error, stop = None, False
|
|
275
298
|
try:
|
|
276
299
|
stop = callback is not None and callback(iteration, residual, volume) is False
|
|
277
300
|
except Exception as caught:
|
|
278
301
|
error = caught
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return stop
|
|
302
|
+
failed, stop = control_status((int(error is not None), int(stop)),
|
|
303
|
+
process_group=getattr(operator, "process_group", None),
|
|
304
|
+
world_size=getattr(operator, "world_size", 1))
|
|
305
|
+
if failed:
|
|
306
|
+
agreement(operator, error)
|
|
307
|
+
return bool(stop)
|