Skip to content

Commit f9617f0

Browse files
committed
Update controller_demo.ipynb
1 parent 772e216 commit f9617f0

1 file changed

Lines changed: 0 additions & 130 deletions

File tree

docs/tutorials/control/controller_demo.ipynb

Lines changed: 0 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -662,136 +662,6 @@
662662
" \"Black-box sub-stepped SDE, partial observation, particle filter (PFConfig)\",\n",
663663
")"
664664
]
665-
},
666-
{
667-
"cell_type": "markdown",
668-
"id": "64e249ae",
669-
"metadata": {},
670-
"source": [
671-
"## 7. A minimal black-box example\n",
672-
"\n",
673-
"Section 6's black box wraps a genuine sub-stepped SDE integration -- fairly involved. Here's the\n",
674-
"smallest possible black box: a one-line nonlinear transition and a non-Gaussian observation, with\n",
675-
"no solver calls at all.\n",
676-
"\n",
677-
"$$\n",
678-
"\\begin{aligned}\n",
679-
"x_{k+1} &= (x_k + \\eta_k)^2 + u_k, \\qquad \\eta_k \\sim \\mathcal{N}(0, \\sigma^2 I) \\\\\n",
680-
"y_k &= H x_k + \\varepsilon_k, \\qquad \\varepsilon_k \\sim \\mathrm{Laplace}(0, b)\n",
681-
"\\end{aligned}\n",
682-
"$$\n",
683-
"\n",
684-
"The noise enters *inside* the square, so $x_{k+1}$ is non-Gaussian even though $\\eta_k$ itself is\n",
685-
"simple Gaussian -- there's no `mean + additive noise` form to exploit, so the transition has to be\n",
686-
"a black box again: a tiny class exposing only `.sample(key)`/`.shape()`, exactly like\n",
687-
"`SubSteppedSDEStep` above, just without any sub-stepping.\n",
688-
"\n",
689-
"The observation noise is explicitly **non-Gaussian** (Laplace) to make the point that the\n",
690-
"observation model isn't restricted to Gaussian noise either -- but note this one *can't* be a bare\n",
691-
"`.sample()`-only black box like the transition: cuthbert's particle filter weights particles via\n",
692-
"`observation_model(x, u, t).log_prob(y)`, so it needs a real `numpyro.distributions.Distribution`\n",
693-
"with a working density, not just a sampler."
694-
]
695-
},
696-
{
697-
"cell_type": "code",
698-
"execution_count": 25,
699-
"id": "41a85aee",
700-
"metadata": {
701-
"execution": {
702-
"iopub.execute_input": "2026-08-05T18:22:45.882623Z",
703-
"iopub.status.busy": "2026-08-05T18:22:45.882561Z",
704-
"iopub.status.idle": "2026-08-05T18:22:45.892487Z",
705-
"shell.execute_reply": "2026-08-05T18:22:45.892168Z"
706-
}
707-
},
708-
"outputs": [],
709-
"source": [
710-
"class MinimalBlackBoxTransition:\n",
711-
" \"\"\"f(x, u, t_now, t_next) = (x + noise)^2 + u -- noise enters *inside*\n",
712-
" the square, so x_next is non-Gaussian even though the noise itself is\n",
713-
" simple Gaussian. Only .sample()/.shape() are exposed, same minimal\n",
714-
" contract as SubSteppedSDEStep above.\"\"\"\n",
715-
"\n",
716-
" def __init__(self, x, u, noise_std):\n",
717-
" self._x, self._u, self._noise_std = x, u, noise_std\n",
718-
"\n",
719-
" def sample(self, key):\n",
720-
" noise = self._noise_std * jr.normal(key, self._x.shape)\n",
721-
" return (self._x + noise) ** 2 + self._u\n",
722-
"\n",
723-
" def shape(self):\n",
724-
" return self._x.shape\n",
725-
"\n",
726-
"\n",
727-
"def minimal_black_box_transition(x, u, t_now, t_next):\n",
728-
" return MinimalBlackBoxTransition(x, u, noise_std=0.1)\n",
729-
"\n",
730-
"\n",
731-
"def minimal_black_box_observation(x, u, t):\n",
732-
" # Laplace noise -- genuinely non-Gaussian, unlike every other observation\n",
733-
" # model in this notebook. Needs a real Distribution (not a bare .sample()\n",
734-
" # black box like the transition above): PF weights particles via\n",
735-
" # observation_model(x, u, t).log_prob(y).\n",
736-
" return dist.Independent(\n",
737-
" dist.Laplace(loc=jnp.eye(obs_dim_2d, state_dim_2d) @ x, scale=0.1), 1\n",
738-
" )\n",
739-
"\n",
740-
"\n",
741-
"minimal_dynamics = DynamicalModel(\n",
742-
" initial_condition=dist.MultivariateNormal(\n",
743-
" jnp.array([3.0, 2.0]), 0.05 * jnp.eye(state_dim_2d)\n",
744-
" ),\n",
745-
" state_evolution=minimal_black_box_transition,\n",
746-
" observation_model=minimal_black_box_observation,\n",
747-
" control_dim=control_dim_2d,\n",
748-
")"
749-
]
750-
},
751-
{
752-
"cell_type": "code",
753-
"execution_count": 26,
754-
"id": "6e1d0eb3",
755-
"metadata": {
756-
"execution": {
757-
"iopub.execute_input": "2026-08-05T18:22:45.893499Z",
758-
"iopub.status.busy": "2026-08-05T18:22:45.893436Z",
759-
"iopub.status.idle": "2026-08-05T18:22:46.526938Z",
760-
"shell.execute_reply": "2026-08-05T18:22:46.526646Z"
761-
}
762-
},
763-
"outputs": [],
764-
"source": [
765-
"# Squaring the state each step is explosive -- no bounded control can cancel\n",
766-
"# quadratic growth once x drifts away from 0, so this stays finite only for a\n",
767-
"# short horizon (a handful of steps already overflows float32).\n",
768-
"minimal_predict_times = jnp.arange(0.0, 0.3, 0.1)\n",
769-
"\n",
770-
"minimal_result = dsx.simulate(\n",
771-
" minimal_dynamics,\n",
772-
" rng_key=jr.PRNGKey(0),\n",
773-
" predict_times=minimal_predict_times,\n",
774-
" control_policy=LinearPolicy(K=0.5 * jnp.eye(control_dim_2d)),\n",
775-
" filter_config=PFConfig(n_particles=500),\n",
776-
")\n",
777-
"assert jnp.all(jnp.isfinite(minimal_result.states))"
778-
]
779-
},
780-
{
781-
"cell_type": "code",
782-
"execution_count": null,
783-
"id": "17de102b",
784-
"metadata": {},
785-
"outputs": [],
786-
"source": []
787-
},
788-
{
789-
"cell_type": "code",
790-
"execution_count": null,
791-
"id": "11e0c759",
792-
"metadata": {},
793-
"outputs": [],
794-
"source": []
795665
}
796666
],
797667
"metadata": {

0 commit comments

Comments
 (0)