27. Transductive API
This page documents the transductive API. For a full run, see the transductive tutorial.
27.1 What it is for
The transductive brick provides registries and utilities for graph-based SSL methods. [1][2]
27.2 Examples
Run label propagation on a small graph:
import numpy as np
from modssc.graph import GraphBuilderSpec, build_graph
from modssc.graph.artifacts import NodeDataset
from modssc.transductive import get_method_class
from modssc.transductive.methods.classic.label_propagation import LabelPropagationSpec
X = np.random.randn(10, 4).astype(np.float32)
G = build_graph(X, spec=GraphBuilderSpec(scheme="knn", metric="cosine", k=3), seed=0, cache=False)
train_mask = np.zeros((10,), dtype=bool)
train_mask[:2] = True
node_ds = NodeDataset(X=X, y=np.zeros((10,), dtype=np.int64), graph=G, masks={"train_mask": train_mask})
method = get_method_class("label_propagation")(spec=LabelPropagationSpec())
method.fit(node_ds)
proba = method.predict_proba(node_ds)
print(proba.shape)
List available method IDs:
from modssc.transductive import available_methods
print(available_methods())
The method registry is defined in src/modssc/transductive/registry.py. [1]
27.3 API reference
Transductive semi supervised learning.
This package provides the math and integration layer:
- backend abstraction (numpy, torch)
- graph operators (normalization, laplacian, spmm)
- generic solvers (fixed point, conjugate gradient)
- PyG adapter (optional)
- strict input validation
Algorithms (Label Propagation, Poisson Learning, GNNs, etc.) are added in later waves.
27.4
DeviceSpec
dataclass
Device and dtype settings.
device
- cpu: always CPU
- cuda: use CUDA (error if unavailable)
- mps: use Apple MPS (error if unavailable)
- auto: pick cuda if available, else mps, else cpu
dtype: numeric precision to use for math operators
Source code in src/modssc/transductive/types.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | @dataclass(frozen=True)
class DeviceSpec:
"""Device and dtype settings.
device:
- cpu: always CPU
- cuda: use CUDA (error if unavailable)
- mps: use Apple MPS (error if unavailable)
- auto: pick cuda if available, else mps, else cpu
dtype: numeric precision to use for math operators
"""
device: DeviceName = "cpu"
dtype: DTypeName = "float32"
|
27.5
OptionalDependencyError
dataclass
Bases: ImportError
Raised when an optional dependency (extra) is required but missing.
Source code in src/modssc/transductive/errors.py
6
7
8
9
10
11
12
13
14
15
16 | @dataclass(frozen=True)
class OptionalDependencyError(ImportError):
"""Raised when an optional dependency (extra) is required but missing."""
package: str
extra: str
message: str | None = None
def __str__(self) -> str:
base = self.message or f"Optional dependency {self.package!r} is required."
return f'{base} Install with: pip install "modssc[{self.extra}]"'
|
27.6
TransductiveValidationError
Bases: ValueError
Raised when inputs are invalid for transductive methods.
Source code in src/modssc/transductive/errors.py
| class TransductiveValidationError(ValueError):
"""Raised when inputs are invalid for transductive methods."""
|
27.7
get_method_info(method_id)
Return the :class:~modssc.transductive.base.MethodInfo for a method.
Source code in src/modssc/transductive/registry.py
133
134
135
136
137
138
139 | def get_method_info(method_id: str) -> MethodInfo:
"""Return the :class:`~modssc.transductive.base.MethodInfo` for a method."""
cls = get_method_class(method_id)
info = getattr(cls, "info", None)
if not isinstance(info, MethodInfo):
raise TypeError(f"Method class {cls} must expose a class attribute `info: MethodInfo`")
return info
|
27.8
register_method(method_id, import_path, *, status='implemented')
Register a method by id and a lazy import string.
Source code in src/modssc/transductive/registry.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 | def register_method(
method_id: str,
import_path: str,
*,
status: Literal["implemented", "planned"] = "implemented",
) -> None:
"""Register a method by id and a lazy import string."""
if not method_id or not isinstance(method_id, str):
raise ValueError("method_id must be a non-empty string")
if ":" not in import_path:
raise ValueError("import_path must be of the form 'pkg.module:ClassName'")
existing = _REGISTRY.get(method_id)
if existing is not None and existing.import_path != import_path:
raise ValueError(
f"method_id {method_id!r} already registered with import_path={existing.import_path!r}"
)
if status not in {"implemented", "planned"}:
raise ValueError("status must be 'implemented' or 'planned'")
_REGISTRY[method_id] = MethodRef(method_id=method_id, import_path=import_path, status=status)
|
27.9
validate_node_dataset(data)
Validate the minimal invariants needed by transductive algorithms.
Source code in src/modssc/transductive/validation.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 | def validate_node_dataset(data: NodeDatasetLike) -> None:
"""Validate the minimal invariants needed by transductive algorithms."""
if data is None:
raise TransductiveValidationError("data must not be None")
X = _as_numpy(data.X)
y = _as_numpy(data.y)
if X.ndim != 2:
raise TransductiveValidationError(f"X must be 2D (n, d), got shape {X.shape}")
if y.ndim != 1:
raise TransductiveValidationError(f"y must be 1D (n,), got shape {y.shape}")
if X.shape[0] != y.shape[0]:
raise TransductiveValidationError("X and y must have the same first dimension")
y_arr = np.asarray(y)
if np.issubdtype(y_arr.dtype, np.integer):
y_int = y_arr.astype(np.int64, copy=False)
elif np.issubdtype(y_arr.dtype, np.floating):
if not np.isfinite(y_arr).all():
raise TransductiveValidationError(
"y must contain finite integer class ids; found non-finite values"
)
y_int = y_arr.astype(np.int64)
if not np.all(y_arr == y_int):
raise TransductiveValidationError(
"y must contain integer class ids; run preprocess step 'labels.encode'"
)
else:
raise TransductiveValidationError(
"y must contain integer class ids; run preprocess step 'labels.encode'"
)
y_valid = y_int[y_int >= 0]
if y_valid.size:
classes = np.unique(y_valid)
if not np.array_equal(classes, np.arange(int(classes.size))):
raise TransductiveValidationError(
"y must contain contiguous class ids starting at 0; run preprocess step 'labels.encode'"
)
if data.graph is None:
raise TransductiveValidationError("data.graph must not be None")
raw_edge_index = getattr(data.graph, "edge_index", None)
if raw_edge_index is None:
raise TransductiveValidationError("graph.edge_index is required")
edge_index = _as_numpy(raw_edge_index)
if edge_index.ndim != 2 or edge_index.shape[0] != 2:
raise TransductiveValidationError(
f"edge_index must have shape (2, E), got {edge_index.shape}"
)
n = int(X.shape[0])
if edge_index.size > 0 and (edge_index.min() < 0 or edge_index.max() >= n):
raise TransductiveValidationError("edge_index has out of range node indices")
masks: Mapping[str, Any] = data.masks or {}
for key in ("train_mask", "val_mask", "test_mask", "unlabeled_mask"):
if key not in masks:
continue
m = _as_numpy(masks[key]).astype(bool)
if m.shape != (n,):
raise TransductiveValidationError(f"{key} must have shape (n,), got {m.shape}")
|
Sources
src/modssc/transductive/registry.py
src/modssc/transductive/base.py