Networks
Five built-in architectures, dispatched by NetworkConfig.type:
type |
Module | Use case |
|---|---|---|
mlp |
mlp.MLP, MultiHeadMLP, ResMLP |
Most models; default |
lstm |
lstm.LSTMPolicy |
History-dependent policies, history_len > 1 |
transformer |
transformer.TransformerPolicy |
Same; multi-head attention over history window |
linear_plus_mlp |
linear_plus_mlp.LinearPlusMLP |
policy = linear(state) + mlp(state); init at the BK linearization |
kf_anchored_mlp |
kf_anchored_mlp |
CMR-class disaster: K/F outputs pinned to BK anchor |
All factories take (n_states, n_policies, hidden_sizes, ..., key) and
return an Equinox eqx.Module whose __call__(state) -> policy works on
both [n_states] and [batch, n_states] inputs (via jax.vmap of a
per-sample helper).
Output bounds are enforced at the network output, per-dimension:
- Finite
policy_upper[i]→ sigmoid scaled to[lower, upper]. policy_upper[i] = jnp.inf→softplus(x) + lower.
Shared utilities live in networks/common.py: _normalize_input
(input shift/scale stop_gradient), _apply_bounds (the
sigmoid/softplus dispatch), INIT_FNS (the init-name → init-fn
table).
For adding a new network type, see Adding a network.
deqn_jax.networks.mlp
MLP policy network using Equinox.
MLP
Bases: Module
Multi-layer perceptron for policy approximation.
Outputs are optionally bounded using sigmoid scaling
output = lower + (upper - lower) * sigmoid(raw_output)
Attributes:
| Name | Type | Description |
|---|---|---|
layers |
list
|
List of linear layers |
activations |
tuple
|
Per-layer activation functions (one per hidden layer) |
output_lower |
Optional[tuple]
|
Lower bounds for outputs [n_outputs] |
output_upper |
Optional[tuple]
|
Upper bounds for outputs [n_outputs] |
input_shift |
Optional[tuple]
|
Input normalization shift (subtracted) [n_inputs] |
input_scale |
Optional[tuple]
|
Input normalization scale (divided) [n_inputs] |
Source code in src/deqn_jax/networks/mlp.py
21 22 23 24 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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
__call__
__call__(x: Array) -> Array
Forward pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Array
|
Input tensor [batch, in_features] or [in_features] |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Output tensor [batch, out_features] or [out_features] |
Source code in src/deqn_jax/networks/mlp.py
106 107 108 109 110 111 112 113 114 115 116 117 118 | |
ResMLP
Bases: Module
MLP with residual (skip) connections between hidden layers.
Each hidden layer computes: h_{i+1} = act(W_i @ h_i + b_i) + proj(h_i) where proj is identity if sizes match, or a learned linear projection if hidden sizes differ.
This improves gradient flow and lets the network learn corrections rather than full mappings — helpful for multi-equation PINNs.
Source code in src/deqn_jax/networks/mlp.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
MultiHeadMLP
Bases: Module
MLP with separate output heads per policy variable.
Shared trunk → per-policy linear heads. Gives each policy its own output parameters, reducing gradient interference between equations that depend on different policies.
Source code in src/deqn_jax/networks/mlp.py
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
create_mlp
create_mlp(
n_states: int,
n_policies: int,
hidden_sizes: Sequence[int] = (64, 64),
activation: str = "tanh",
activations: Optional[Sequence[str]] = None,
init: str = "default",
policy_lower: Optional[Array] = None,
policy_upper: Optional[Array] = None,
multi_head: bool = False,
skip_connections: bool = False,
input_shift: Optional[Array] = None,
input_scale: Optional[Array] = None,
*,
key: Array,
) -> eqx.Module
Factory function to create MLP with common configurations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_states
|
int
|
Number of state variables (input dimension) |
required |
n_policies
|
int
|
Number of policy variables (output dimension) |
required |
hidden_sizes
|
Sequence[int]
|
Tuple of hidden layer sizes |
(64, 64)
|
activation
|
str
|
Activation name for all hidden layers (default: "tanh") |
'tanh'
|
activations
|
Optional[Sequence[str]]
|
Per-layer activation names (overrides activation if set) |
None
|
init
|
str
|
Weight initialization ("xavier_normal", "xavier_uniform", "he_normal", "he_uniform", "lecun_normal", "default") |
'default'
|
policy_lower
|
Optional[Array]
|
Lower bounds for policy outputs |
None
|
policy_upper
|
Optional[Array]
|
Upper bounds for policy outputs |
None
|
skip_connections
|
bool
|
Use residual connections between hidden layers |
False
|
key
|
Array
|
JAX PRNG key |
required |
Returns:
| Type | Description |
|---|---|
Module
|
Initialized MLP model |
Source code in src/deqn_jax/networks/mlp.py
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | |
deqn_jax.networks.lstm
Multi-layer LSTM policy network using Equinox.
Takes a history window of states [B, H, D] and outputs policy [B, P] for the current (last) timestep. Supports: - Multi-layer stacked LSTM with configurable depth - Input normalization (frozen via stop_gradient) - Mixed softplus/sigmoid output bounding (via common._apply_bounds)
LSTMPolicy
Bases: Module
Multi-layer LSTM for history-dependent policy approximation.
Input: [batch, history_len, n_states] or [history_len, n_states] Output: [batch, n_policies] or [n_policies]
Architecture
input [D] -> Linear -> hidden_size -> LSTMCell layer 1 -> ... -> LSTMCell layer L -> final hidden state -> Linear -> n_policies -> bounds
Source code in src/deqn_jax/networks/lstm.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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
__call__
__call__(x: Array) -> Array
Forward pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Array
|
[batch, history_len, n_states] or [history_len, n_states] |
required |
Returns:
| Type | Description |
|---|---|
Array
|
[batch, n_policies] or [n_policies] |
Source code in src/deqn_jax/networks/lstm.py
131 132 133 134 135 136 137 138 139 140 141 142 143 | |
create_lstm
create_lstm(
n_states: int,
n_policies: int,
hidden_sizes: Sequence[int] = (64,),
history_len: int = 10,
policy_lower: Optional[Array] = None,
policy_upper: Optional[Array] = None,
input_shift: Optional[Array] = None,
input_scale: Optional[Array] = None,
*,
key: Array,
) -> LSTMPolicy
Factory function to create multi-layer LSTM policy network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_states
|
int
|
Number of state variables |
required |
n_policies
|
int
|
Number of policy variables |
required |
hidden_sizes
|
Sequence[int]
|
Tuple of hidden sizes (one per LSTM layer) |
(64,)
|
history_len
|
int
|
Length of history window |
10
|
policy_lower
|
Optional[Array]
|
Lower bounds for outputs |
None
|
policy_upper
|
Optional[Array]
|
Upper bounds for outputs |
None
|
input_shift
|
Optional[Array]
|
Input normalization shift |
None
|
input_scale
|
Optional[Array]
|
Input normalization scale |
None
|
key
|
Array
|
PRNG key |
required |
Returns:
| Type | Description |
|---|---|
LSTMPolicy
|
Initialized LSTMPolicy |
Source code in src/deqn_jax/networks/lstm.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
deqn_jax.networks.transformer
Transformer policy network using Equinox.
Takes a history window of states [B, H, D] and outputs policy [B, P] for the current (last) timestep. Supports: - Learned positional embeddings - Pre-LN transformer blocks (LayerNorm before attention/FFN) - Mixed softplus/sigmoid output bounding (via common._apply_bounds)
MultiHeadSelfAttention
Bases: Module
Simple multi-head self-attention without dropout.
Avoids eqx.nn.MultiheadAttention whose Dropout module has a
non-static inference boolean that breaks jax.jit tracing.
Source code in src/deqn_jax/networks/transformer.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 | |
__call__
__call__(x: Array) -> Array
Self-attention on [seq_len, hidden_dim].
Source code in src/deqn_jax/networks/transformer.py
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 | |
TransformerBlock
Bases: Module
Pre-LN Transformer block: LayerNorm -> Attention -> Residual -> LayerNorm -> FFN -> Residual.
Source code in src/deqn_jax/networks/transformer.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
__call__
__call__(x: Array) -> Array
Forward pass for single sequence [seq_len, hidden_dim].
Source code in src/deqn_jax/networks/transformer.py
98 99 100 101 102 103 104 105 106 107 108 109 110 | |
TransformerPolicy
Bases: Module
Transformer for history-dependent policy approximation.
Input: [batch, history_len, n_states] or [history_len, n_states] Output: [batch, n_policies] or [n_policies]
Architecture
input [H, D] -> normalize -> Linear -> [H, hidden_dim] + learned positional embeddings -> N TransformerBlocks -> extract last timestep [hidden_dim] -> Linear -> [n_policies] -> bounds
Source code in src/deqn_jax/networks/transformer.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
__call__
__call__(x: Array) -> Array
Forward pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Array
|
[batch, history_len, n_states] or [history_len, n_states] |
required |
Returns:
| Type | Description |
|---|---|
Array
|
[batch, n_policies] or [n_policies] |
Source code in src/deqn_jax/networks/transformer.py
204 205 206 207 208 209 210 211 212 213 214 215 216 | |
create_transformer
create_transformer(
n_states: int,
n_policies: int,
hidden_dim: int = 64,
n_layers: int = 2,
num_heads: int = 4,
history_len: int = 10,
policy_lower: Optional[Array] = None,
policy_upper: Optional[Array] = None,
input_shift: Optional[Array] = None,
input_scale: Optional[Array] = None,
*,
key: Array,
) -> TransformerPolicy
Factory function to create Transformer policy network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_states
|
int
|
Number of state variables |
required |
n_policies
|
int
|
Number of policy variables |
required |
hidden_dim
|
int
|
Transformer hidden dimension (must be divisible by num_heads) |
64
|
n_layers
|
int
|
Number of transformer blocks |
2
|
num_heads
|
int
|
Number of attention heads |
4
|
history_len
|
int
|
Length of history window |
10
|
policy_lower
|
Optional[Array]
|
Lower bounds for outputs |
None
|
policy_upper
|
Optional[Array]
|
Upper bounds for outputs |
None
|
input_shift
|
Optional[Array]
|
Input normalization shift |
None
|
input_scale
|
Optional[Array]
|
Input normalization scale |
None
|
key
|
Array
|
PRNG key |
required |
Returns:
| Type | Description |
|---|---|
TransformerPolicy
|
Initialized TransformerPolicy |
Source code in src/deqn_jax/networks/transformer.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
deqn_jax.networks.linear_plus_mlp
Residual policy: analytically-added linear policy + MLP correction.
Two output parameterizations are supported, selectable per-policy via
output_links:
-
"linear"(default, original behavior): policy_i(s) = ss_i + P_i^level @ (s - ss_state) + delta_mlp_i(s) -
"log"(positive-multiplicative around SS): policy_i(s) = ss_i * exp(P_i^log @ (s - ss_state) + delta_mlp_i(s))where P_i^log = P_i^level / ss_i (delta-method conversion of the BK matrix). At init (init_scale=0) and at s=ss_state, both forms reduce to ss_i exactly. The log form bakes in positivity and is the natural parameterization for log-deviations-from-SS, which is the standard DSGE convention (cf. Dynare's log-linearized solutions).
The linear part is the Dynare-linearized (Blanchard-Kahn) solution, which solves the model to first order and is correct by construction near SS. The MLP starts at zero (via a scaled final layer) so at initialization the full policy IS the linear policy (in level or log space depending on the per-policy link). Training only learns corrections on top.
This architecture solves a specific PINN pathology observed when networks are trained directly against equilibrium residuals: a bare MLP can converge to a degenerate fixed point where policies collapse to low values, even when that produces dynamics far from the true SS. A residual parameterization inherits the linear policy's correctness as a floor — the network can only help, never hurt.
The MLP is UNBOUNDED (no sigmoid/softplus on output). Instead, the linear baseline + small MLP corrections keep policies in the valid region. Hard clipping to policy bounds happens at the very end to prevent catastrophic policy outputs during early training.
This module is model-agnostic. Per-model shape priors (e.g. K/F
gauge masking, ELB feature augmentation, output-space reparameterizations
to encode equation-specific curvature) live in the model's own
network.py module, not here. See models/disaster/network.py for
the disaster-specific shape priors layered on top of this core.
LinearPlusMLP
Bases: Module
Generic residual ansatz: policy = clip(π_BK + δ_θ, lower, upper).
Attributes:
| Name | Type | Description |
|---|---|---|
mlp |
MLP
|
Unbounded MLP that outputs the correction (delta) in output space. |
P |
Array
|
Policy rule matrix from linearization [n_policies, n_states]. |
ss_state |
Array
|
Steady-state state vector [n_states]. |
ss_policy |
Array
|
Steady-state policy vector [n_policies]. |
policy_lower |
/ policy_upper
|
Hard clipping bounds (safety fence; the linear + delta should normally keep policies well inside these). |
The forward pass is intentionally minimal: linearization, MLP delta, sum, hard clip. Per-model shape priors should be implemented in a model-specific subclass or wrapper that lives next to the model definition, not bolted onto this generic core.
Source code in src/deqn_jax/networks/linear_plus_mlp.py
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | |
create_linear_plus_mlp
create_linear_plus_mlp(
model,
hidden_sizes: Sequence[int] = (128, 128),
activation: str = "tanh",
init: str = "default",
init_scale: float = 0.01,
input_shift: Optional[Array] = None,
input_scale: Optional[Array] = None,
output_links: Optional[Sequence[str]] = None,
*,
key: Array,
) -> LinearPlusMLP
Factory: build a generic LinearPlusMLP using the model's linearization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_links
|
Optional[Sequence[str]]
|
per-policy parameterization. None defaults to all-linear
(legacy behavior). Each entry must be |
None
|
This factory has NO model-specific knobs. For disaster-specific shape
priors (K/F gauge mask, ELB feature, q-as-M reparameterization), use
create_disaster_policy_net from deqn_jax.models.disaster.network.
Source code in src/deqn_jax/networks/linear_plus_mlp.py
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | |