gds-framework 0.2.0__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.
gds/__init__.py ADDED
@@ -0,0 +1,175 @@
1
+ """Generalized Dynamical Systems — typed compositional specifications for complex systems.
2
+
3
+ GDS synthesizes ideas from GDS theory (Roxin, Zargham & Shorish), MSML (BlockScience),
4
+ BDP-lib (Block Diagram Protocol), and categorical cybernetics (Ghani, Hedges et al.)
5
+ into a single, dependency-light Python framework.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ # ── Composition algebra ─────────────────────────────────────
11
+ from gds.blocks.base import AtomicBlock, Block
12
+ from gds.blocks.composition import (
13
+ FeedbackLoop,
14
+ ParallelComposition,
15
+ StackComposition,
16
+ TemporalLoop,
17
+ Wiring,
18
+ )
19
+ from gds.blocks.errors import GDSCompositionError, GDSError, GDSTypeError
20
+
21
+ # ── GDS block roles ─────────────────────────────────────────
22
+ from gds.blocks.roles import (
23
+ BoundaryAction,
24
+ ControlAction,
25
+ HasConstraints,
26
+ HasOptions,
27
+ HasParams,
28
+ Mechanism,
29
+ Policy,
30
+ )
31
+
32
+ # ── Canonical projection ───────────────────────────────────
33
+ from gds.canonical import CanonicalGDS, project_canonical
34
+ from gds.compiler.compile import compile_system
35
+
36
+ # ── Convenience helpers ────────────────────────────────────
37
+ from gds.helpers import (
38
+ all_checks,
39
+ entity,
40
+ gds_check,
41
+ get_custom_checks,
42
+ interface,
43
+ space,
44
+ state_var,
45
+ typedef,
46
+ )
47
+
48
+ # ── IR and serialization ────────────────────────────────────
49
+ from gds.ir.models import (
50
+ BlockIR,
51
+ CompositionType,
52
+ FlowDirection,
53
+ HierarchyNodeIR,
54
+ SystemIR,
55
+ WiringIR,
56
+ )
57
+ from gds.ir.serialization import IRDocument, IRMetadata, load_ir, save_ir
58
+
59
+ # ── Parameters ─────────────────────────────────────────────
60
+ from gds.parameters import ParameterDef, ParameterSchema
61
+
62
+ # ── Query engine ────────────────────────────────────────────
63
+ from gds.query import SpecQuery
64
+ from gds.serialize import spec_to_dict, spec_to_json
65
+
66
+ # ── Typed spaces and state ──────────────────────────────────
67
+ from gds.spaces import EMPTY, TERMINAL, Space
68
+
69
+ # ── Specification registry ──────────────────────────────────
70
+ from gds.spec import GDSSpec, SpecWiring, Wire
71
+ from gds.state import Entity, StateVariable
72
+ from gds.tagged import Tagged
73
+ from gds.types.interface import Interface, Port, port
74
+ from gds.types.tokens import tokenize, tokens_overlap, tokens_subset
75
+
76
+ # ── Type system ─────────────────────────────────────────────
77
+ from gds.types.typedef import (
78
+ AgentID,
79
+ NonNegativeFloat,
80
+ PositiveInt,
81
+ Probability,
82
+ Timestamp,
83
+ TokenAmount,
84
+ TypeDef,
85
+ )
86
+
87
+ # ── Verification ────────────────────────────────────────────
88
+ from gds.verification.engine import verify
89
+ from gds.verification.findings import Finding, Severity, VerificationReport
90
+ from gds.verification.spec_checks import (
91
+ check_canonical_wellformedness,
92
+ check_completeness,
93
+ check_determinism,
94
+ check_parameter_references,
95
+ check_reachability,
96
+ check_type_safety,
97
+ )
98
+
99
+ __all__ = [
100
+ "EMPTY",
101
+ "TERMINAL",
102
+ "AgentID",
103
+ "AtomicBlock",
104
+ "Block",
105
+ "BlockIR",
106
+ "BoundaryAction",
107
+ "CanonicalGDS",
108
+ "CompositionType",
109
+ "ControlAction",
110
+ "Entity",
111
+ "FeedbackLoop",
112
+ "Finding",
113
+ "FlowDirection",
114
+ "GDSCompositionError",
115
+ "GDSError",
116
+ "GDSSpec",
117
+ "GDSTypeError",
118
+ "HasConstraints",
119
+ "HasOptions",
120
+ "HasParams",
121
+ "HierarchyNodeIR",
122
+ "IRDocument",
123
+ "IRMetadata",
124
+ "Interface",
125
+ "Mechanism",
126
+ "NonNegativeFloat",
127
+ "ParallelComposition",
128
+ "ParameterDef",
129
+ "ParameterSchema",
130
+ "Policy",
131
+ "Port",
132
+ "PositiveInt",
133
+ "Probability",
134
+ "Severity",
135
+ "Space",
136
+ "SpecQuery",
137
+ "SpecWiring",
138
+ "StackComposition",
139
+ "StateVariable",
140
+ "SystemIR",
141
+ "Tagged",
142
+ "TemporalLoop",
143
+ "Timestamp",
144
+ "TokenAmount",
145
+ "TypeDef",
146
+ "VerificationReport",
147
+ "Wire",
148
+ "Wiring",
149
+ "WiringIR",
150
+ "all_checks",
151
+ "check_canonical_wellformedness",
152
+ "check_completeness",
153
+ "check_determinism",
154
+ "check_parameter_references",
155
+ "check_reachability",
156
+ "check_type_safety",
157
+ "compile_system",
158
+ "entity",
159
+ "gds_check",
160
+ "get_custom_checks",
161
+ "interface",
162
+ "load_ir",
163
+ "port",
164
+ "project_canonical",
165
+ "save_ir",
166
+ "space",
167
+ "spec_to_dict",
168
+ "spec_to_json",
169
+ "state_var",
170
+ "tokenize",
171
+ "tokens_overlap",
172
+ "tokens_subset",
173
+ "typedef",
174
+ "verify",
175
+ ]
gds/blocks/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """Block primitives, composition operators, and GDS roles."""
2
+
3
+ from gds.blocks.base import AtomicBlock, Block
4
+ from gds.blocks.composition import (
5
+ FeedbackLoop,
6
+ ParallelComposition,
7
+ StackComposition,
8
+ TemporalLoop,
9
+ Wiring,
10
+ )
11
+ from gds.blocks.errors import GDSCompositionError, GDSError, GDSTypeError
12
+ from gds.blocks.roles import BoundaryAction, ControlAction, Mechanism, Policy
13
+
14
+ __all__ = [
15
+ "AtomicBlock",
16
+ "Block",
17
+ "BoundaryAction",
18
+ "ControlAction",
19
+ "FeedbackLoop",
20
+ "GDSCompositionError",
21
+ "GDSError",
22
+ "GDSTypeError",
23
+ "Mechanism",
24
+ "ParallelComposition",
25
+ "Policy",
26
+ "StackComposition",
27
+ "TemporalLoop",
28
+ "Wiring",
29
+ ]
gds/blocks/base.py ADDED
@@ -0,0 +1,91 @@
1
+ """Abstract base class for Blocks in the GDS framework.
2
+
3
+ A Block is the fundamental composable unit. It has a name and an Interface
4
+ (directional port pairs). Composition operators build composite blocks
5
+ from simpler ones, forming a tree that the compiler flattens into a list
6
+ of atomic blocks + wiring.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import TYPE_CHECKING
13
+
14
+ from gds.tagged import Tagged
15
+ from gds.types.interface import Interface
16
+
17
+ if TYPE_CHECKING:
18
+ from gds.blocks.composition import (
19
+ FeedbackLoop,
20
+ ParallelComposition,
21
+ StackComposition,
22
+ TemporalLoop,
23
+ Wiring,
24
+ )
25
+
26
+
27
+ class Block(Tagged, ABC):
28
+ """Abstract base for all Blocks — both atomic and composite.
29
+
30
+ Every block has a ``name`` and an ``interface`` describing its boundary
31
+ ports. Composition operators (``>>``, ``|``, ``.feedback()``, ``.loop()``)
32
+ build composite blocks from simpler ones.
33
+ """
34
+
35
+ name: str
36
+ interface: Interface = Interface()
37
+
38
+ @abstractmethod
39
+ def flatten(self) -> list[AtomicBlock]:
40
+ """Return all atomic blocks in evaluation order."""
41
+
42
+ def __rshift__(self, other: Block) -> StackComposition:
43
+ """``a >> b`` — stack (sequential) composition."""
44
+ from gds.blocks.composition import StackComposition
45
+
46
+ return StackComposition(
47
+ name=f"{self.name} >> {other.name}",
48
+ first=self,
49
+ second=other,
50
+ )
51
+
52
+ def __or__(self, other: Block) -> ParallelComposition:
53
+ """``a | b`` — parallel composition."""
54
+ from gds.blocks.composition import ParallelComposition
55
+
56
+ return ParallelComposition(
57
+ name=f"{self.name} | {other.name}",
58
+ left=self,
59
+ right=other,
60
+ )
61
+
62
+ def feedback(self, wiring: list[Wiring]) -> FeedbackLoop:
63
+ """Wrap with backward feedback within a single timestep."""
64
+ from gds.blocks.composition import FeedbackLoop
65
+
66
+ return FeedbackLoop(
67
+ name=f"{self.name} [feedback]",
68
+ inner=self,
69
+ feedback_wiring=wiring,
70
+ )
71
+
72
+ def loop(self, wiring: list[Wiring], exit_condition: str = "") -> TemporalLoop:
73
+ """Wrap with forward temporal iteration across timesteps."""
74
+ from gds.blocks.composition import TemporalLoop
75
+
76
+ return TemporalLoop(
77
+ name=f"{self.name} [loop]",
78
+ inner=self,
79
+ temporal_wiring=wiring,
80
+ exit_condition=exit_condition,
81
+ )
82
+
83
+
84
+ class AtomicBlock(Block):
85
+ """Base class for non-decomposable (leaf) blocks.
86
+
87
+ Domain packages subclass this to define their own atomic block types.
88
+ """
89
+
90
+ def flatten(self) -> list[AtomicBlock]:
91
+ return [self]
@@ -0,0 +1,141 @@
1
+ """Composition operators: stack, parallel, feedback, and temporal loop.
2
+
3
+ These operators combine blocks into larger composite systems:
4
+
5
+ - **Stack** (``>>``) — chains blocks so one's output feeds another's input.
6
+ - **Parallel** (``|``) — runs blocks side-by-side with no shared wires.
7
+ - **Feedback** — connects backward_out to backward_in within a single timestep.
8
+ - **Temporal Loop** — connects forward_out to forward_in across timesteps.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Self
14
+
15
+ from pydantic import BaseModel, Field, model_validator
16
+
17
+ from gds.blocks.base import AtomicBlock, Block
18
+ from gds.blocks.errors import GDSTypeError
19
+ from gds.ir.models import FlowDirection
20
+ from gds.types.interface import Interface, Port
21
+
22
+
23
+ class Wiring(BaseModel, frozen=True):
24
+ """An explicit connection between two blocks.
25
+
26
+ Covariant wirings (the default) carry data forward; contravariant
27
+ wirings carry feedback backward.
28
+ """
29
+
30
+ source_block: str
31
+ source_port: str
32
+ target_block: str
33
+ target_port: str
34
+ direction: FlowDirection = FlowDirection.COVARIANT
35
+
36
+
37
+ class StackComposition(Block):
38
+ """``a >> b`` — stack (sequential) composition.
39
+
40
+ Output of the first block feeds input of the second. If no explicit
41
+ ``wiring`` is provided, the validator checks that forward_out tokens
42
+ overlap with forward_in tokens.
43
+ """
44
+
45
+ first: Block
46
+ second: Block
47
+ wiring: list[Wiring] = Field(default_factory=list)
48
+
49
+ @model_validator(mode="after")
50
+ def _compute_interface_and_validate(self) -> Self:
51
+ if not self.wiring:
52
+ first_out_tokens = _collect_tokens(self.first.interface.forward_out)
53
+ second_in_tokens = _collect_tokens(self.second.interface.forward_in)
54
+
55
+ if first_out_tokens and second_in_tokens and not (first_out_tokens & second_in_tokens):
56
+ raise GDSTypeError(
57
+ f"Stack composition {self.name!r}: "
58
+ f"first.forward_out tokens {first_out_tokens} have no overlap with "
59
+ f"second.forward_in tokens {second_in_tokens}"
60
+ )
61
+
62
+ self.interface = Interface(
63
+ forward_in=self.first.interface.forward_in + self.second.interface.forward_in,
64
+ forward_out=self.first.interface.forward_out + self.second.interface.forward_out,
65
+ backward_in=self.first.interface.backward_in + self.second.interface.backward_in,
66
+ backward_out=self.first.interface.backward_out + self.second.interface.backward_out,
67
+ )
68
+ return self
69
+
70
+ def flatten(self) -> list[AtomicBlock]:
71
+ return self.first.flatten() + self.second.flatten()
72
+
73
+
74
+ class ParallelComposition(Block):
75
+ """``a | b`` — parallel composition: blocks run independently."""
76
+
77
+ left: Block
78
+ right: Block
79
+
80
+ @model_validator(mode="after")
81
+ def _compute_interface(self) -> Self:
82
+ self.interface = Interface(
83
+ forward_in=self.left.interface.forward_in + self.right.interface.forward_in,
84
+ forward_out=self.left.interface.forward_out + self.right.interface.forward_out,
85
+ backward_in=self.left.interface.backward_in + self.right.interface.backward_in,
86
+ backward_out=self.left.interface.backward_out + self.right.interface.backward_out,
87
+ )
88
+ return self
89
+
90
+ def flatten(self) -> list[AtomicBlock]:
91
+ return self.left.flatten() + self.right.flatten()
92
+
93
+
94
+ class FeedbackLoop(Block):
95
+ """Backward feedback within a single timestep (backward_out -> backward_in)."""
96
+
97
+ inner: Block
98
+ feedback_wiring: list[Wiring]
99
+
100
+ @model_validator(mode="after")
101
+ def _validate_and_compute_interface(self) -> Self:
102
+ self.interface = self.inner.interface
103
+ return self
104
+
105
+ def flatten(self) -> list[AtomicBlock]:
106
+ return self.inner.flatten()
107
+
108
+
109
+ class TemporalLoop(Block):
110
+ """Forward temporal iteration across timesteps (forward_out -> forward_in).
111
+
112
+ All temporal wiring must be covariant direction.
113
+ """
114
+
115
+ inner: Block
116
+ temporal_wiring: list[Wiring]
117
+ exit_condition: str = ""
118
+
119
+ @model_validator(mode="after")
120
+ def _validate_and_compute_interface(self) -> Self:
121
+ for w in self.temporal_wiring:
122
+ if w.direction != FlowDirection.COVARIANT:
123
+ raise GDSTypeError(
124
+ f"TemporalLoop {self.name!r}: temporal wiring "
125
+ f"{w.source_block}.{w.source_port} → {w.target_block}.{w.target_port} "
126
+ f"must be COVARIANT (got {w.direction.value})"
127
+ )
128
+
129
+ self.interface = self.inner.interface
130
+ return self
131
+
132
+ def flatten(self) -> list[AtomicBlock]:
133
+ return self.inner.flatten()
134
+
135
+
136
+ def _collect_tokens(ports: tuple[Port, ...]) -> frozenset[str]:
137
+ """Collect all type tokens from a tuple of ports."""
138
+ tokens: set[str] = set()
139
+ for p in ports:
140
+ tokens.update(p.type_tokens)
141
+ return frozenset(tokens)
gds/blocks/errors.py ADDED
@@ -0,0 +1,17 @@
1
+ """GDS-specific error types.
2
+
3
+ Raised at construction time during Pydantic validation when a block
4
+ or composition violates structural constraints.
5
+ """
6
+
7
+
8
+ class GDSError(Exception):
9
+ """Base class for all GDS errors."""
10
+
11
+
12
+ class GDSTypeError(GDSError):
13
+ """Port type mismatch or invalid port structure during construction."""
14
+
15
+
16
+ class GDSCompositionError(GDSError):
17
+ """Invalid composition structure."""
gds/blocks/roles.py ADDED
@@ -0,0 +1,129 @@
1
+ """GDS block roles from the MSML decomposition.
2
+
3
+ These roles decompose the transition function h into typed components:
4
+
5
+ - **BoundaryAction** — exogenous input (GDS admissible inputs U)
6
+ - **ControlAction** — endogenous control (reads state, emits signals)
7
+ - **Policy** — decision logic (maps signals to mechanism inputs)
8
+ - **Mechanism** — state update (the only component that writes state)
9
+
10
+ Each role subclasses AtomicBlock, inheriting composition operators and
11
+ flatten(). Role-specific validators enforce structural constraints.
12
+
13
+ Protocols
14
+ ---------
15
+ ``HasParams``, ``HasConstraints``, and ``HasOptions`` describe the structural
16
+ attributes shared by multiple role types without requiring a common base class.
17
+ Use ``isinstance(block, HasParams)`` for safe narrowing instead of
18
+ ``cast(Any, block)`` or ``hasattr`` guards.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Protocol, Self, runtime_checkable
24
+
25
+ from pydantic import Field, model_validator
26
+
27
+ from gds.blocks.base import AtomicBlock
28
+ from gds.blocks.errors import GDSCompositionError
29
+
30
+
31
+ @runtime_checkable
32
+ class HasParams(Protocol):
33
+ """Structural protocol for blocks that declare parameter dependencies."""
34
+
35
+ params_used: list[str]
36
+
37
+
38
+ @runtime_checkable
39
+ class HasConstraints(Protocol):
40
+ """Structural protocol for blocks that carry constraint annotations."""
41
+
42
+ constraints: list[str]
43
+
44
+
45
+ @runtime_checkable
46
+ class HasOptions(Protocol):
47
+ """Structural protocol for blocks that enumerate named strategy options."""
48
+
49
+ options: list[str]
50
+
51
+
52
+ class BoundaryAction(AtomicBlock):
53
+ """Exogenous input — enters the system from outside.
54
+
55
+ In GDS terms: part of the admissible input set U.
56
+ Boundary actions model external agents, oracles, user inputs,
57
+ environmental signals — anything the system doesn't control.
58
+
59
+ Enforces ``forward_in = ()`` since boundary actions receive no
60
+ internal forward signals.
61
+ """
62
+
63
+ kind: str = "boundary"
64
+ options: list[str] = Field(default_factory=list)
65
+ params_used: list[str] = Field(default_factory=list)
66
+ constraints: list[str] = Field(default_factory=list)
67
+
68
+ @model_validator(mode="after")
69
+ def _enforce_no_forward_in(self) -> Self:
70
+ if self.interface.forward_in:
71
+ raise GDSCompositionError(
72
+ f"BoundaryAction {self.name!r}: forward_in must be empty "
73
+ f"(boundary actions receive no internal forward signals)"
74
+ )
75
+ return self
76
+
77
+
78
+ class ControlAction(AtomicBlock):
79
+ """Endogenous control — reads state, emits control signals.
80
+
81
+ These are internal feedback loops: the system observing itself
82
+ and generating signals that influence downstream policy/mechanism blocks.
83
+ """
84
+
85
+ kind: str = "control"
86
+ options: list[str] = Field(default_factory=list)
87
+ params_used: list[str] = Field(default_factory=list)
88
+ constraints: list[str] = Field(default_factory=list)
89
+
90
+
91
+ class Policy(AtomicBlock):
92
+ """Decision logic — maps signals to mechanism inputs.
93
+
94
+ Policies select from feasible actions. Named options support
95
+ scenario analysis and A/B testing.
96
+
97
+ In GDS terms: policies implement the decision mapping
98
+ within the admissibility constraint.
99
+ """
100
+
101
+ kind: str = "policy"
102
+ options: list[str] = Field(default_factory=list)
103
+ params_used: list[str] = Field(default_factory=list)
104
+ constraints: list[str] = Field(default_factory=list)
105
+
106
+
107
+ class Mechanism(AtomicBlock):
108
+ """State update — the only block type that writes to state.
109
+
110
+ Mechanisms are the atomic state transitions that compose into h.
111
+ They have no backward ports (state writes don't propagate signals).
112
+
113
+ ``updates`` lists (entity_name, variable_name) pairs specifying
114
+ which state variables this mechanism modifies.
115
+ """
116
+
117
+ kind: str = "mechanism"
118
+ updates: list[tuple[str, str]] = Field(default_factory=list)
119
+ params_used: list[str] = Field(default_factory=list)
120
+ constraints: list[str] = Field(default_factory=list)
121
+
122
+ @model_validator(mode="after")
123
+ def _enforce_no_backward(self) -> Self:
124
+ if self.interface.backward_in or self.interface.backward_out:
125
+ raise GDSCompositionError(
126
+ f"Mechanism {self.name!r}: backward ports must be empty "
127
+ f"(mechanisms write state, they don't pass backward signals)"
128
+ )
129
+ return self