diff --git a/.gitignore b/.gitignore index b98ac7e..b58bd19 100644 --- a/.gitignore +++ b/.gitignore @@ -108,10 +108,7 @@ runs/ **/wandb/ # Falcon-specific output directories -**/default-store-dir/ -**/default-graph-dir/ -**/outputs/ -**/*.zarr/ +**/output/ # Data files #*.npz diff --git a/examples/01_minimal/config.yml b/examples/01_minimal/config.yml index 8458eeb..218b762 100644 --- a/examples/01_minimal/config.yml +++ b/examples/01_minimal/config.yml @@ -76,25 +76,21 @@ graph: estimator: # Posterior estimator network _target_: falcon.estimators.Flow # Flow-based posterior estimation - loop: # Training loop parameters - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 # Early stopping patience - network: # Neural network architecture - net_type: nsf # Neural spline flow (alternatives: zuko_gf, maf, naf, etc.) - theta_norm: true # Normalize parameter space - norm_momentum: 0.003 # Momentum for online normalization updates + max_epochs: 300 + net_type: nsf # Neural spline flow (alternatives: zuko_gf, maf, naf, etc.) + lr: 0.01 + gamma: 0.5 # Mixing coefficient for amortization weighting embedding: # Neural embedding for observation x _target_: model.E _input_: [x] - optimizer: # Optimizer parameters - lr: 0.01 - lr_decay_factor: 0.5 # LR decay multiplier - scheduler_patience: 16 # LR decay after N stagnant epochs - inference: # Inference and sampling parameters - gamma: 0.5 # Mixing coefficient for amortization weighting - discard_samples: false - log_ratio_threshold: -20 # Stability cutoff for log ratios + batch_size: 128 + early_stop_patience: 32 + theta_norm: true # Normalize parameter space + norm_momentum: 0.003 # Momentum for online normalization updates + lr_decay_factor: 0.5 # LR decay multiplier + lr_patience: 16 # LR decay after N stagnant epochs + discard_samples: false + log_ratio_threshold: -20 # Stability cutoff for log ratios ray: num_gpus: 0 # GPU count per Ray worker (0 = CPU) diff --git a/examples/01_minimal/notebook.ipynb b/examples/01_minimal/notebook.ipynb new file mode 100644 index 0000000..0f035c3 --- /dev/null +++ b/examples/01_minimal/notebook.ipynb @@ -0,0 +1,671 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "99781568", + "metadata": {}, + "source": [ + "# 01 — Minimal Falcon run (notebook API)\n", + "\n", + "This notebook shows the simplest way to run Falcon from Python/Colab:\n", + "load a config, optionally tweak a parameter, launch training, and inspect\n", + "the result. The matching CLI command is:\n", + "\n", + "```bash\n", + "cd examples/01_minimal\n", + "falcon launch -o output/my_run\n", + "```\n", + "\n", + "**Prerequisites**: install Falcon and its dependencies, then run this\n", + "notebook from the `examples/01_minimal/` directory so that the relative\n", + "paths in `config.yml` resolve correctly." + ] + }, + { + "cell_type": "markdown", + "id": "8002b0ce", + "metadata": {}, + "source": [ + "## 1. Load the config" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5ba40301", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```yaml\n", + "logging:\n", + " wandb:\n", + " enabled: false\n", + " project: falcon_examples\n", + " group: 01_minimal\n", + " dir: ${run_dir}\n", + " local:\n", + " enabled: true\n", + " dir: ${paths.graph}\n", + "paths:\n", + " imports:\n", + " - ./src\n", + " graph: ${run_dir}/graph\n", + " samples: ${run_dir}/samples\n", + "buffer:\n", + " min_samples: 4096\n", + " max_samples: 32768\n", + " validation_samples: 256\n", + " simulate_count: 64\n", + " simulate_when_full: true\n", + " simulate_interval: 1\n", + " snapshot_every: 10\n", + "graph:\n", + " z:\n", + " evidence:\n", + " - x\n", + " simulator:\n", + " _target_: falcon.priors.Hypercube\n", + " priors:\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " estimator:\n", + " _target_: falcon.estimators.Flow\n", + " loop:\n", + " max_epochs: 300\n", + " batch_size: 128\n", + " early_stop_patience: 32\n", + " network:\n", + " net_type: nsf\n", + " theta_norm: true\n", + " norm_momentum: 0.003\n", + " embedding:\n", + " _target_: model.E\n", + " _input_:\n", + " - x\n", + " optimizer:\n", + " lr: 0.01\n", + " lr_decay_factor: 0.5\n", + " scheduler_patience: 16\n", + " inference:\n", + " gamma: 0.5\n", + " discard_samples: false\n", + " log_ratio_threshold: -20\n", + " ray:\n", + " num_gpus: 0\n", + " x:\n", + " parents:\n", + " - z\n", + " simulator:\n", + " _target_: model.Simulate\n", + " npar: 3\n", + " observed: ./data/mock_data.npz['x']\n", + "sample:\n", + " posterior:\n", + " 'n': 1000\n", + "\n", + "```" + ], + "text/plain": [ + "Config(\n", + "logging:\n", + " wandb:\n", + " enabled: false\n", + " project: falcon_examples\n", + " group: 01_minimal\n", + " dir: ${run_dir}\n", + " local:\n", + " enabled: true\n", + " dir: ${paths.graph}\n", + "paths:\n", + " imports:\n", + " - ./src\n", + " graph: ${run_dir}/graph\n", + " samples: ${run_dir}/samples\n", + "buffer:\n", + " min_samples: 4096\n", + " max_samples: 32768\n", + " validation_samples: 256\n", + " simulate_count: 64\n", + " simulate_when_full: true\n", + " simulate_interval: 1\n", + " snapshot_every: 10\n", + "graph:\n", + " z:\n", + " evidence:\n", + " - x\n", + " simulator:\n", + " _target_: falcon.priors.Hypercube\n", + " priors:\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " estimator:\n", + " _target_: falcon.estimators.Flow\n", + " loop:\n", + " max_epochs: 300\n", + " batch_size: 128\n", + " early_stop_patience: 32\n", + " network:\n", + " net_type: nsf\n", + " theta_norm: true\n", + " norm_momentum: 0.003\n", + " embedding:\n", + " _target_: model.E\n", + " _input_:\n", + " - x\n", + " optimizer:\n", + " lr: 0.01\n", + " lr_decay_factor: 0.5\n", + " scheduler_patience: 16\n", + " inference:\n", + " gamma: 0.5\n", + " discard_samples: false\n", + " log_ratio_threshold: -20\n", + " ray:\n", + " num_gpus: 0\n", + " x:\n", + " parents:\n", + " - z\n", + " simulator:\n", + " _target_: model.Simulate\n", + " npar: 3\n", + " observed: ./data/mock_data.npz['x']\n", + "sample:\n", + " posterior:\n", + " 'n': 1000\n", + ")" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import falcon\n", + "\n", + "cfg = falcon.config(\"config.yml\")\n", + "cfg # rich repr renders the full YAML in Jupyter" + ] + }, + { + "cell_type": "markdown", + "id": "f0dbca9f", + "metadata": {}, + "source": [ + "## 2. Override parameters for a quick demo run\n", + "\n", + "`override()` returns a new `Config`; the original is unchanged.\n", + "Use dotted paths matching the YAML structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1982d332", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```yaml\n", + "logging:\n", + " wandb:\n", + " enabled: false\n", + " project: falcon_examples\n", + " group: 01_minimal\n", + " dir: ${run_dir}\n", + " local:\n", + " enabled: true\n", + " dir: ${paths.graph}\n", + "paths:\n", + " imports:\n", + " - ./src\n", + " graph: ${run_dir}/graph\n", + " samples: ${run_dir}/samples\n", + "buffer:\n", + " min_samples: 4096\n", + " max_samples: 32768\n", + " validation_samples: 64\n", + " simulate_count: 64\n", + " simulate_when_full: true\n", + " simulate_interval: 1\n", + " snapshot_every: 10\n", + "graph:\n", + " z:\n", + " evidence:\n", + " - x\n", + " simulator:\n", + " _target_: falcon.priors.Hypercube\n", + " priors:\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " estimator:\n", + " _target_: falcon.estimators.Flow\n", + " loop:\n", + " max_epochs: 20\n", + " batch_size: 128\n", + " early_stop_patience: 50\n", + " network:\n", + " net_type: nsf\n", + " theta_norm: true\n", + " norm_momentum: 0.003\n", + " embedding:\n", + " _target_: model.E\n", + " _input_:\n", + " - x\n", + " optimizer:\n", + " lr: 0.01\n", + " lr_decay_factor: 0.5\n", + " scheduler_patience: 16\n", + " inference:\n", + " gamma: 0.5\n", + " discard_samples: false\n", + " log_ratio_threshold: -20\n", + " ray:\n", + " num_gpus: 0.3\n", + " x:\n", + " parents:\n", + " - z\n", + " simulator:\n", + " _target_: model.Simulate\n", + " npar: 3\n", + " observed: ./data/mock_data.npz['x']\n", + "sample:\n", + " posterior:\n", + " 'n': 10000\n", + "\n", + "```" + ], + "text/plain": [ + "Config(\n", + "logging:\n", + " wandb:\n", + " enabled: false\n", + " project: falcon_examples\n", + " group: 01_minimal\n", + " dir: ${run_dir}\n", + " local:\n", + " enabled: true\n", + " dir: ${paths.graph}\n", + "paths:\n", + " imports:\n", + " - ./src\n", + " graph: ${run_dir}/graph\n", + " samples: ${run_dir}/samples\n", + "buffer:\n", + " min_samples: 4096\n", + " max_samples: 32768\n", + " validation_samples: 64\n", + " simulate_count: 64\n", + " simulate_when_full: true\n", + " simulate_interval: 1\n", + " snapshot_every: 10\n", + "graph:\n", + " z:\n", + " evidence:\n", + " - x\n", + " simulator:\n", + " _target_: falcon.priors.Hypercube\n", + " priors:\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " - - uniform\n", + " - -100.0\n", + " - 100.0\n", + " estimator:\n", + " _target_: falcon.estimators.Flow\n", + " loop:\n", + " max_epochs: 20\n", + " batch_size: 128\n", + " early_stop_patience: 50\n", + " network:\n", + " net_type: nsf\n", + " theta_norm: true\n", + " norm_momentum: 0.003\n", + " embedding:\n", + " _target_: model.E\n", + " _input_:\n", + " - x\n", + " optimizer:\n", + " lr: 0.01\n", + " lr_decay_factor: 0.5\n", + " scheduler_patience: 16\n", + " inference:\n", + " gamma: 0.5\n", + " discard_samples: false\n", + " log_ratio_threshold: -20\n", + " ray:\n", + " num_gpus: 0.3\n", + " x:\n", + " parents:\n", + " - z\n", + " simulator:\n", + " _target_: model.Simulate\n", + " npar: 3\n", + " observed: ./data/mock_data.npz['x']\n", + "sample:\n", + " posterior:\n", + " 'n': 10000\n", + ")" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cfg = cfg.override(\n", + " \"buffer.min_samples=512\",\n", + " \"buffer.max_samples=1024\",\n", + " \"buffer.validation_samples=64\",\n", + " \"graph.z.estimator.loop.max_epochs=20\",\n", + " \"graph.z.estimator.loop.early_stop_patience=50\",\n", + " \"graph.z.ray.num_gpus=0.3\",\n", + " \"sample.posterior.n=1000\",\n", + ")\n", + "cfg" + ] + }, + { + "cell_type": "markdown", + "id": "42ff1816", + "metadata": {}, + "source": [ + "## 3. Launch training\n", + "\n", + "`falcon.launch()` blocks until training completes and returns a `Run`\n", + "object pointing at the output directory. Ray is started automatically\n", + "on the first call if it is not already running." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c41270d0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/weniger/.local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n", + "2026-06-08 14:27:14,241\tINFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-08T14:27:18 [INFO] falcon v0.4.3.dev10+gbbaf70e3c.d20260608\n", + "2026-06-08T14:27:18 [INFO] Output: output/notebook_run3\n", + "2026-06-08T14:27:18 [INFO] Ray: 145.136.62.39:58813 (new local instance)\n", + "2026-06-08T14:27:18 [INFO] Resources: 72 CPU, 1 GPU, 317.9 GB\n", + "2026-06-08T14:27:18 [INFO] Falcon graph structure:\n", + " Node name List of parents Class name\n", + "* z <- | falcon.priors.Hypercube\n", + "* x <- z | model.Simulate \n", + "\n", + "2026-06-08T14:27:18 [INFO] Observed: x [1, 3]\n", + "2026-06-08T14:27:18 [INFO] Spinning up graph...\n", + "2026-06-08T14:27:21 [INFO] ✓ z\n", + "2026-06-08T14:27:23 [INFO] ✓ x\n", + "2026-06-08T14:27:26 [INFO] Generating 4160 initial samples...\n", + "2026-06-08T14:27:29 [ERROR] \u001b[36m(DatasetManagerActor pid=405947)\u001b[0m Using blocking ray.get inside async actor. This blocks the event loop. Please use `await` on object ref with asyncio.gather if you want to yield execution to the event loop instead.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[36m(DatasetManagerActor pid=405947)\u001b[0m Using blocking ray.get inside async actor. This blocks the event loop. Please use `await` on object ref with asyncio.gather if you want to yield execution to the event loop instead.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-08T14:27:30 [INFO] Initial samples ready (0 loaded, 4160 generated)\n", + "2026-06-08T14:27:30 [INFO] \n", + "2026-06-08T14:27:30 [INFO] Starting analysis. Monitor with: falcon monitor\n", + "2026-06-08T14:27:30 [INFO] [z] Training started\n", + "2026-06-08T14:28:33 [INFO] [z] epoch 15/20, loss -7.83\n", + "2026-06-08T14:28:33 [INFO] Buffer: 5504 train, 64 val (5568 total)\n", + "2026-06-08T14:28:52 [INFO] [z] Training completed (loss: -3.9760)\n", + "2026-06-08T14:28:52 [INFO] \n", + "2026-06-08T14:28:52 [INFO] Analysis completed.\n", + "2026-06-08T14:28:52 [INFO] Generating 10000 posterior samples...\n", + "2026-06-08T14:30:31 [INFO] ============================================================\n", + "2026-06-08T14:30:31 [INFO] falcon launch failed (RayTaskError(OutOfMemoryError): \u001b[36mray::NodeWrapper.sample_posterior()\u001b[39m (pid=405936, ip=145.136.62.39, actor_id=837d11a5132792cd1f3844c901000000, repr=)\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 438, in result\n", + " return self.__get_result()\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 390, in __get_result\n", + " raise self._exception\n", + " File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 348, in sample_posterior\n", + " return self._chunked_sample(n_samples, condition_refs, self._sample_posterior)\n", + " File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 320, in _chunked_sample\n", + " output = method(end - start, chunk)\n", + " File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 398, in _sample_posterior\n", + " return self.estimator_instance.sample_posterior(n_samples, conditions=conditions)\n", + " File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 429, in sample_posterior\n", + " samples, logprob = self._importance_sample(num_samples, mode=\"posterior\", conditions=conditions or {})\n", + " File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 499, in _importance_sample\n", + " log_prob_marg = marginal_net.log_prob(samples_proposals, s * 0)\n", + " File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow_density.py\", line 96, in log_prob\n", + " log_prob = self.net.log_prob(theta.float(), condition=s.float())\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/sbi/neural_nets/estimators/nflows_flow.py\", line 109, in log_prob\n", + " log_probs = self.net.log_prob(input, context=condition)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/distributions/base.py\", line 40, in log_prob\n", + " return self._log_prob(inputs, context)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/flows/base.py\", line 39, in _log_prob\n", + " noise, logabsdet = self._transform(inputs, context=embedded_context)\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n", + " return self._call_impl(*args, **kwargs)\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n", + " return forward_call(*args, **kwargs)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 56, in forward\n", + " return self._cascade(inputs, funcs, context)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 50, in _cascade\n", + " outputs, logabsdet = func(outputs, context)\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n", + " return self._call_impl(*args, **kwargs)\n", + " File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n", + " return forward_call(*args, **kwargs)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 84, in forward\n", + " transform_split, logabsdet = self._coupling_transform_forward(\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 194, in _coupling_transform_forward\n", + " return self._coupling_transform(inputs, transform_params, inverse=False)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 211, in _coupling_transform\n", + " outputs, logabsdet = self._piecewise_cdf(inputs, transform_params, inverse)\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 492, in _piecewise_cdf\n", + " return spline_fn(\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 46, in unconstrained_rational_quadratic_spline\n", + " ) = rational_quadratic_spline(\n", + " File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 92, in rational_quadratic_spline\n", + " cumwidths = (right - left) * cumwidths + left\n", + "torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 216.00 MiB. GPU 0 has a total capacity of 39.49 GiB of which 147.25 MiB is free. Including non-PyTorch memory, this process has 39.34 GiB memory in use. Of the allocated memory 37.85 GiB is allocated by PyTorch, and 1011.64 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables))\n", + "2026-06-08T14:30:31 [INFO] Output: output/notebook_run3\n", + "2026-06-08T14:30:31 [INFO] Samples: output/notebook_run3/samples\n", + "2026-06-08T14:30:31 [INFO] Logs: output/notebook_run3/graph/driver/output.log (driver)\n", + "2026-06-08T14:30:31 [INFO] output/notebook_run3/graph//output.log (per-node: z, x)\n", + "2026-06-08T14:30:31 [INFO] Started: 2026-06-08 14:27:18\n", + "2026-06-08T14:30:31 [INFO] Ended: 2026-06-08 14:30:31\n", + "2026-06-08T14:30:31 [INFO] Runtime: 3m 14s\n", + "2026-06-08T14:30:31 [INFO] Ray: 1 node(s) | 72 CPU, 1 GPU, 317.9 GB\n", + "2026-06-08T14:30:31 [INFO] Nodes:\n", + "2026-06-08T14:30:31 [INFO] z: done | 20/20 epochs | loss=-3.976 | 5952 sims\n", + "2026-06-08T14:30:31 [INFO] x: idle\n", + "2026-06-08T14:30:31 [INFO] Samples generated: 6080 total | 6016 train, 64 val (6080 live in buffer)\n", + "2026-06-08T14:30:31 [INFO] ============================================================\n" + ] + }, + { + "ename": "RayTaskError(OutOfMemoryError)", + "evalue": "\u001b[36mray::NodeWrapper.sample_posterior()\u001b[39m (pid=405936, ip=145.136.62.39, actor_id=837d11a5132792cd1f3844c901000000, repr=)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 438, in result\n return self.__get_result()\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 390, in __get_result\n raise self._exception\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 348, in sample_posterior\n return self._chunked_sample(n_samples, condition_refs, self._sample_posterior)\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 320, in _chunked_sample\n output = method(end - start, chunk)\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 398, in _sample_posterior\n return self.estimator_instance.sample_posterior(n_samples, conditions=conditions)\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 429, in sample_posterior\n samples, logprob = self._importance_sample(num_samples, mode=\"posterior\", conditions=conditions or {})\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 499, in _importance_sample\n log_prob_marg = marginal_net.log_prob(samples_proposals, s * 0)\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow_density.py\", line 96, in log_prob\n log_prob = self.net.log_prob(theta.float(), condition=s.float())\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/sbi/neural_nets/estimators/nflows_flow.py\", line 109, in log_prob\n log_probs = self.net.log_prob(input, context=condition)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/distributions/base.py\", line 40, in log_prob\n return self._log_prob(inputs, context)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/flows/base.py\", line 39, in _log_prob\n noise, logabsdet = self._transform(inputs, context=embedded_context)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n return self._call_impl(*args, **kwargs)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n return forward_call(*args, **kwargs)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 56, in forward\n return self._cascade(inputs, funcs, context)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 50, in _cascade\n outputs, logabsdet = func(outputs, context)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n return self._call_impl(*args, **kwargs)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n return forward_call(*args, **kwargs)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 84, in forward\n transform_split, logabsdet = self._coupling_transform_forward(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 194, in _coupling_transform_forward\n return self._coupling_transform(inputs, transform_params, inverse=False)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 211, in _coupling_transform\n outputs, logabsdet = self._piecewise_cdf(inputs, transform_params, inverse)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 492, in _piecewise_cdf\n return spline_fn(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 46, in unconstrained_rational_quadratic_spline\n ) = rational_quadratic_spline(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 92, in rational_quadratic_spline\n cumwidths = (right - left) * cumwidths + left\ntorch.OutOfMemoryError: CUDA out of memory. Tried to allocate 216.00 MiB. GPU 0 has a total capacity of 39.49 GiB of which 147.25 MiB is free. Including non-PyTorch memory, this process has 39.34 GiB memory in use. Of the allocated memory 37.85 GiB is allocated by PyTorch, and 1011.64 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRayTaskError(OutOfMemoryError)\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m run \u001b[38;5;241m=\u001b[39m \u001b[43mfalcon\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlaunch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcfg\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moutput\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43moutput/notebook_run3\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2\u001b[0m run\n", + "File \u001b[0;32m/gpfs/home2/weniger/falcon/falcon/api.py:310\u001b[0m, in \u001b[0;36mlaunch\u001b[0;34m(target, output, overrides, auto_sample, timeout, wait)\u001b[0m\n\u001b[1;32m 307\u001b[0m init()\n\u001b[1;32m 309\u001b[0m obs \u001b[38;5;241m=\u001b[39m prebuilt_graph\u001b[38;5;241m.\u001b[39m_api_observations \u001b[38;5;28;01mif\u001b[39;00m prebuilt_graph \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 310\u001b[0m \u001b[43m_run_pipeline\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 311\u001b[0m \u001b[43m \u001b[49m\u001b[43mcfg\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 312\u001b[0m \u001b[43m \u001b[49m\u001b[43mauto_sample\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mauto_sample\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 313\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 314\u001b[0m \u001b[43m \u001b[49m\u001b[43mgraph\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mprebuilt_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 315\u001b[0m \u001b[43m \u001b[49m\u001b[43mobservations\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mobs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 316\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 318\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m load_run(output_dir)\n", + "File \u001b[0;32m/gpfs/home2/weniger/falcon/falcon/cli.py:621\u001b[0m, in \u001b[0;36m_run_pipeline\u001b[0;34m(cfg, auto_sample, timeout, stop_check, log_handler, on_graph_ready, summary_sink, graph, observations)\u001b[0m\n\u001b[1;32m 619\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m auto_sample \u001b[38;5;129;01mand\u001b[39;00m num_posterior_samples \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m 620\u001b[0m info(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mGenerating \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnum_posterior_samples\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m posterior samples...\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 621\u001b[0m sample_refs \u001b[38;5;241m=\u001b[39m \u001b[43mdeployed_graph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msample_posterior\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnum_posterior_samples\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mobservations_tensors\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 622\u001b[0m samples \u001b[38;5;241m=\u001b[39m deployed_graph\u001b[38;5;241m.\u001b[39m_refs_to_arrays(sample_refs)\n\u001b[1;32m 623\u001b[0m _save_samples(samples\u001b[38;5;241m=\u001b[39msamples, sample_cfg\u001b[38;5;241m=\u001b[39msample_cfg, sample_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mposterior\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 624\u001b[0m graph\u001b[38;5;241m=\u001b[39mgraph, cfg\u001b[38;5;241m=\u001b[39mcfg, info_fn\u001b[38;5;241m=\u001b[39minfo)\n", + "File \u001b[0;32m/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py:703\u001b[0m, in \u001b[0;36mDeployedGraph.sample_posterior\u001b[0;34m(self, num_samples, conditions)\u001b[0m\n\u001b[1;32m 693\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Run posterior sampling through the inference graph.\u001b[39;00m\n\u001b[1;32m 694\u001b[0m \n\u001b[1;32m 695\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 700\u001b[0m \u001b[38;5;124;03m List[Dict[str, ObjectRef]]: One dict per sample with refs to all node values\u001b[39;00m\n\u001b[1;32m 701\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 702\u001b[0m condition_refs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_arrays_to_condition_refs(conditions, num_samples) \u001b[38;5;28;01mif\u001b[39;00m conditions \u001b[38;5;28;01melse\u001b[39;00m {}\n\u001b[0;32m--> 703\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_execute_graph\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 704\u001b[0m \u001b[43m \u001b[49m\u001b[43mnum_samples\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgraph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward_order\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcondition_refs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43msample_posterior\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 705\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py:666\u001b[0m, in \u001b[0;36mDeployedGraph._execute_graph\u001b[0;34m(self, num_samples, node_order, condition_refs, sample_method)\u001b[0m\n\u001b[1;32m 663\u001b[0m node_condition_refs[evidence] \u001b[38;5;241m=\u001b[39m ref_trace[evidence]\n\u001b[1;32m 665\u001b[0m remote_method \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mwrapped_nodes_dict[name], sample_method)\n\u001b[0;32m--> 666\u001b[0m node_refs \u001b[38;5;241m=\u001b[39m \u001b[43mray\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 667\u001b[0m \u001b[43m \u001b[49m\u001b[43mremote_method\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mremote\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnum_samples\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcondition_refs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnode_condition_refs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 668\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 670\u001b[0m \u001b[38;5;66;03m# Update trace with value refs for downstream nodes\u001b[39;00m\n\u001b[1;32m 671\u001b[0m ref_trace[name] \u001b[38;5;241m=\u001b[39m [d[\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.value\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;28;01mfor\u001b[39;00m d \u001b[38;5;129;01min\u001b[39;00m node_refs]\n", + "File \u001b[0;32m~/.conda/envs/emri_few_timm/lib/python3.10/site-packages/ray/_private/auto_init_hook.py:21\u001b[0m, in \u001b[0;36mwrap_auto_init..auto_init_wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 19\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mauto_init_wrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 20\u001b[0m auto_init_ray()\n\u001b[0;32m---> 21\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.conda/envs/emri_few_timm/lib/python3.10/site-packages/ray/_private/client_mode_hook.py:103\u001b[0m, in \u001b[0;36mclient_mode_hook..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 101\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m func\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124minit\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m is_client_mode_enabled_by_default:\n\u001b[1;32m 102\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(ray, func\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m)(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m--> 103\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.conda/envs/emri_few_timm/lib/python3.10/site-packages/ray/_private/worker.py:2771\u001b[0m, in \u001b[0;36mget\u001b[0;34m(object_refs, timeout)\u001b[0m\n\u001b[1;32m 2765\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[1;32m 2766\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mInvalid type of object refs, \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mtype\u001b[39m(object_refs)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m, is given. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 2767\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mobject_refs\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m must either be an ObjectRef or a list of ObjectRefs. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 2768\u001b[0m )\n\u001b[1;32m 2770\u001b[0m \u001b[38;5;66;03m# TODO(ujvl): Consider how to allow user to retrieve the ready objects.\u001b[39;00m\n\u001b[0;32m-> 2771\u001b[0m values, debugger_breakpoint \u001b[38;5;241m=\u001b[39m \u001b[43mworker\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_objects\u001b[49m\u001b[43m(\u001b[49m\u001b[43mobject_refs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2772\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, value \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(values):\n\u001b[1;32m 2773\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(value, RayError):\n", + "File \u001b[0;32m~/.conda/envs/emri_few_timm/lib/python3.10/site-packages/ray/_private/worker.py:919\u001b[0m, in \u001b[0;36mWorker.get_objects\u001b[0;34m(self, object_refs, timeout, return_exceptions, skip_deserialization)\u001b[0m\n\u001b[1;32m 917\u001b[0m global_worker\u001b[38;5;241m.\u001b[39mcore_worker\u001b[38;5;241m.\u001b[39mdump_object_store_memory_usage()\n\u001b[1;32m 918\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(value, RayTaskError):\n\u001b[0;32m--> 919\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m value\u001b[38;5;241m.\u001b[39mas_instanceof_cause()\n\u001b[1;32m 920\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 921\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m value\n", + "\u001b[0;31mRayTaskError(OutOfMemoryError)\u001b[0m: \u001b[36mray::NodeWrapper.sample_posterior()\u001b[39m (pid=405936, ip=145.136.62.39, actor_id=837d11a5132792cd1f3844c901000000, repr=)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 438, in result\n return self.__get_result()\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/concurrent/futures/_base.py\", line 390, in __get_result\n raise self._exception\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 348, in sample_posterior\n return self._chunked_sample(n_samples, condition_refs, self._sample_posterior)\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 320, in _chunked_sample\n output = method(end - start, chunk)\n File \"/gpfs/home2/weniger/falcon/falcon/core/deployed_graph.py\", line 398, in _sample_posterior\n return self.estimator_instance.sample_posterior(n_samples, conditions=conditions)\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 429, in sample_posterior\n samples, logprob = self._importance_sample(num_samples, mode=\"posterior\", conditions=conditions or {})\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow.py\", line 499, in _importance_sample\n log_prob_marg = marginal_net.log_prob(samples_proposals, s * 0)\n File \"/gpfs/home2/weniger/falcon/falcon/estimators/flow_density.py\", line 96, in log_prob\n log_prob = self.net.log_prob(theta.float(), condition=s.float())\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/sbi/neural_nets/estimators/nflows_flow.py\", line 109, in log_prob\n log_probs = self.net.log_prob(input, context=condition)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/distributions/base.py\", line 40, in log_prob\n return self._log_prob(inputs, context)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/flows/base.py\", line 39, in _log_prob\n noise, logabsdet = self._transform(inputs, context=embedded_context)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n return self._call_impl(*args, **kwargs)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n return forward_call(*args, **kwargs)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 56, in forward\n return self._cascade(inputs, funcs, context)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/base.py\", line 50, in _cascade\n outputs, logabsdet = func(outputs, context)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1736, in _wrapped_call_impl\n return self._call_impl(*args, **kwargs)\n File \"/home/weniger/.conda/envs/emri_few_timm/lib/python3.10/site-packages/torch/nn/modules/module.py\", line 1747, in _call_impl\n return forward_call(*args, **kwargs)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 84, in forward\n transform_split, logabsdet = self._coupling_transform_forward(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 194, in _coupling_transform_forward\n return self._coupling_transform(inputs, transform_params, inverse=False)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 211, in _coupling_transform\n outputs, logabsdet = self._piecewise_cdf(inputs, transform_params, inverse)\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/coupling.py\", line 492, in _piecewise_cdf\n return spline_fn(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 46, in unconstrained_rational_quadratic_spline\n ) = rational_quadratic_spline(\n File \"/home/weniger/.local/lib/python3.10/site-packages/nflows/transforms/splines/rational_quadratic.py\", line 92, in rational_quadratic_spline\n cumwidths = (right - left) * cumwidths + left\ntorch.OutOfMemoryError: CUDA out of memory. Tried to allocate 216.00 MiB. GPU 0 has a total capacity of 39.49 GiB of which 147.25 MiB is free. Including non-PyTorch memory, this process has 39.34 GiB memory in use. Of the allocated memory 37.85 GiB is allocated by PyTorch, and 1011.64 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)" + ] + } + ], + "source": [ + "run = falcon.launch(cfg, output=\"output/notebook_run3\")\n", + "run" + ] + }, + { + "cell_type": "markdown", + "id": "a219b641", + "metadata": {}, + "source": [ + "## 4. Inspect the result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a7a3f0d", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'run' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Path where everything was written\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mOutput dir:\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[43mrun\u001b[49m\u001b[38;5;241m.\u001b[39mrun_dir)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# Loaded config (identical to what was saved at the start of the run)\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mConfig keys:\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mlist\u001b[39m(run\u001b[38;5;241m.\u001b[39mconfig\u001b[38;5;241m.\u001b[39mkeys()))\n", + "\u001b[0;31mNameError\u001b[0m: name 'run' is not defined" + ] + } + ], + "source": [ + "# Path where everything was written\n", + "print(\"Output dir:\", run.run_dir)\n", + "\n", + "# Loaded config (identical to what was saved at the start of the run)\n", + "print(\"\\nConfig keys:\", list(run.config.keys()))\n", + "\n", + "# Posterior samples (written by auto_sample=True)\n", + "samples = run.samples\n", + "print(\"\\nSamples:\", samples)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7fd92f7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Too few points to create valid contours\n", + "WARNING:root:Too few points to create valid contours\n", + "WARNING:root:Too few points to create valid contours\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAqgAAAKoCAYAAAC7uA1cAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XlUVPX/BvBn2HdCRzFUBJcKTUwypTT3JTRcExXRVFp+LmiWmJZ908pSMRW1zBJMwyVNJEkltXLJck3FZVQkkM0FStkHYYbfH5y5zbDNALNc4Hmd4zkyXO793Fn04bO9JaWlpaUgIiIiIhIJM1M3gIiIiIhIHQMqEREREYkKAyoRERERiQoDKhERERGJCgMqEREREYkKAyoRERERiQoDKhERERGJCgMqEREREYmKhakboA9KpRIZGRlwdHSERCIxdXOIRKW0tBS5ublwc3ODmRl/JyUiIvFrEAE1IyMDrVu3NnUziEQtNTUVrVq1MnUziIiItGoQAdXR0RFA2X/ATk5OJm4Nkbjk5OSgdevWwueEiIhI7BpEQFUN6zs5OTGgElWB01+IiKi+4IQ0IiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhULUzeAxCElJQVZWVlaj5NKpXB3dzdCi4iIiKixYkAlpKSkwMvLCwUFBVqPtbOzg0wmY0glIiIig2FAJWRlZaGgoABRUVHw8vKq8jiZTIagoCBkZWUxoBIREZHBMKCSwMvLCz4+PqZuBhERETVyXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREomJh6gY0FikpKcjKytJ6nFQqhbu7u1GvK5PJ9HY9IiIiorpiQDWClJQUeHl5oaCgQOuxdnZ2kMlkegmpNb2uVCqt8zWJiIiI6ooB1QiysrJQUFCAqKgoeHl5VXmcTCZDUFAQsrKy9BJQdb0uoP+eWyIiIqLaYkA1Ii8vL/j4+DSa6xIRERHVBhdJEREREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqHCbKaJqmKoCGBERUWPGgEpUBVNVACMiImrsGFCJqmCqCmBERESNHQMqkRasxEVERGRcXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwm2m6ildKhzJZDIjtab2TFWpqaE8f0RERA0RA2o9VNMKR1Kp1AitqjlTVWpqKM8fERFRQ8WAWg/pWuEIEHeNeFNVamoozx8REVFDxYBajzWUCkemuo+G8vwRERE1NFwkRURERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosJtpoj0RJfKU9xXlYiISDsGVKI6kkqlsLOzQ1BQkNZj9VkRi4iIqKFiQCWqI3d3d8hkMmRlZVV7nL4rYhERETVUDKhEeuDu7s7QSUREpCdcJEVEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosJ9UEVIW8lMXUpqGpK+rm/q+yAiIiJxYkAVkZqWzJRKpUZo1X9q0j5dmeI+iIiISNwYUEVE15KZQFlYNHblopq0T1emuA8iIiISNwZUkRF7yUyxt4+IiIjqPy6SIiIiIiJRYUAlIiIiIlFhQCUiIiIiUWFAJSIiIiJRYUAlIiIiIlFhQCUiIiIiUeE2U3WUkpKidV9QVkyimtLlfQVwH1kiImqYGFDrICUlBV5eXigoKNB6LCsmka5q+r6SyWQMqURE1KAwoNZBVlYWCgoKEBUVBS8vr2qPZU8X6UrX95VMJkNQUBCysrL43iIiogaFAVUPvLy84OPjY+pmUAPD9xURETVWXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREomJh6gYQNTYymaxO36/p8Xl5eTU6HxERkakxoBIZiVQqhZ2dHYKCgrQea2dnB6lUqrfzERER1ScMqERG4u7uDplMhqysLK3HSqVSuLu76+V858+fxxtvvFGjthIREZkSAyqREbm7u2sNnvo+H4f4iYiovuEiKSIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhVuM0X1Rk0rLBn6PERERGQYDKgkeoaomKRLpSYiIiIyDQZUEr2aVGDSlS6VmoiIiMg0GFCpXtB3BSYiIiISLy6SIiIiIiJRYUAlIiIiIlFhQCUiIiIiUWFAJSIiIiJRYUAlIiIiIlFhQCUiIiIiUWl020ylpKTotJ8m98kkIiIiMo1GFVBTUlLg5eWFgoICrcfa2dlBJpMxpBIREREZWaMKqFlZWSgoKEBUVBS8vLyqPE4mkyEoKAhZWVkMqERERERG1qgCqoqXlxd8fHxM3QwiIiIiqgQXSRERERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoWJi6Afp08eJFODg4VPl9mUxmxNYQERERUW00qIDap08frcfY2dlBKpUaoTVEREREVBsNKqB+/fXXePbZZ6s9RiqVwt3d3UgtIiIiIqKaalAB9cknn4SPj4+pm0FEREREdcBFUkREREQkKgyoRERERCQqDKhEREREJCoMqEREREQkKgyoRERERCQqDKhEREREJCoMqEREREQkKg1qH1R901YalaVTiYiIiPSPAbUSUqkUdnZ2CAoK0nosS6cSERER6RcDaiXc3d0hk8mQlZWl9ViWTiUiIiLSLwbUKri7uzN4EhEREZkAF0kRERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoNIhtpkpLSwEA+fn5yMnJMXFriMQlPz8fwH+fEyIiIrFrEAE1NzcXADB06FATt4RIvHJzc+Hs7GzqZhAREWklKW0A3SpKpRIZGRlwdHSERCKp8c/n5OSgdevWSE1NhZOTkwFaWHdso340xjaWlpYiNzcXbm5uMDPjrB4iIhK/BtGDamZmhlatWtX5PE5OTqINLSpso340tjay55SIiOoTdqcQERERkagwoBIRERGRqDCgArC2tsaHH34Ia2trUzelSmyjfrCNRERE4tcgFkkRERERUcPBHlQiIiIiEhUGVCIiIiISlQaxzVRd90Elash03QeVnyOiqnE/YSLjahABNSMjA61btzZ1M4hELTU1tdr9gvk5ItJO2+eIiPSjQQRUR0dHABB1dSBTefjwocbXEyZMwB9//IF169Zh+PDhwuP5+fmwt7fXer7yxwUGBuLkyZP4/PPPMXDgQNja2gqrzx977LEat69NmzYAgIEDByIiIkLje7qcTx9ee+017N69G66urhg2bBgiIyMBAG+++aZGz8nVq1dx/PhxeHp64vnnn8f27dsBAJmZmbCysjLY+WpKVZlK9TmpCj9HRFXT9XNERPrRIAKqajiyPlQHMjalUqnxtYVF2Utua2ur8VyZmZnBwcFB6/nKH6c6n5OTE9zd3TWO1eW1KN8+FUtLywo/b6zX1tLSEkDZvaoHQ2tra41Aqbr38sc5OTlpfK3v89WWtmF7fo6ItOP0FyLj4ESaRkpfu4txlzIiIiLSNwbURkbVS7dv3746h8u0tDScPXsWwH+9hHXVsWNHAECfPn30cr7aUD1HDx8+1HiOcnJyNI7Lzs4GUNbjeefOnSrPp3puMjMz8eDBA+Hx27dvC39XKpVITU0VznfhwoU63gUREVH9xYDayLz66quQSCSIjY3F/Pnzax1S09LS4O/vj8LCQnh4eKBv3756ad/SpUsRGBiI8ePH6+V8tfF///d/AIDCwkL88ssvwsKhw4cPC89XZmamECI9PDzwyy+/AADc3NwqDMfPmzcPZmZmKCkpwQ8//AAPDw8AwE8//YTk5GQolUocPnwYCQkJkEgkKC0txaVLlwAAnTp10svwPhERUX3CgNrIDB8+HOvWrYNEIsGm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", + "text/plain": [ + "
" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAqgAAAKoCAYAAAC7uA1cAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XlUVPX/BvBn2HdCRzFUBJcKTUwypTT3JTRcExXRVFp+LmiWmJZ908pSMRW1zBJMwyVNJEkltXLJck3FZVQkkM0FStkHYYbfH5y5zbDNALNc4Hmd4zkyXO793Fn04bO9JaWlpaUgIiIiIhIJM1M3gIiIiIhIHQMqEREREYkKAyoRERERiQoDKhERERGJCgMqEREREYkKAyoRERERiQoDKhERERGJCgMqEREREYmKhakboA9KpRIZGRlwdHSERCIxdXOIRKW0tBS5ublwc3ODmRl/JyUiIvFrEAE1IyMDrVu3NnUziEQtNTUVrVq1MnUziIiItGoQAdXR0RFA2X/ATk5OJm4Nkbjk5OSgdevWwueEiIhI7BpEQFUN6zs5OTGgElWB01+IiKi+4IQ0IiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhULUzeAxCElJQVZWVlaj5NKpXB3dzdCi4iIiKixYkAlpKSkwMvLCwUFBVqPtbOzg0wmY0glIiIig2FAJWRlZaGgoABRUVHw8vKq8jiZTIagoCBkZWUxoBIREZHBMKCSwMvLCz4+PqZuBhERETVyXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREomJh6gY0FikpKcjKytJ6nFQqhbu7u1GvK5PJ9HY9IiIiorpiQDWClJQUeHl5oaCgQOuxdnZ2kMlkegmpNb2uVCqt8zWJiIiI6ooB1QiysrJQUFCAqKgoeHl5VXmcTCZDUFAQsrKy9BJQdb0uoP+eWyIiIqLaYkA1Ii8vL/j4+DSa6xIRERHVBhdJEREREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqHCbKaJqmKoCGBERUWPGgEpUBVNVACMiImrsGFCJqmCqCmBERESNHQMqkRasxEVERGRcXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwm2m6ildKhzJZDIjtab2TFWpqaE8f0RERA0RA2o9VNMKR1Kp1AitqjlTVWpqKM8fERFRQ8WAWg/pWuEIEHeNeFNVamoozx8REVFDxYBajzWUCkemuo+G8vwRERE1NFwkRURERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosJtpoj0RJfKU9xXlYiISDsGVKI6kkqlsLOzQ1BQkNZj9VkRi4iIqKFiQCWqI3d3d8hkMmRlZVV7nL4rYhERETVUDKhEeuDu7s7QSUREpCdcJEVEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosJ9UEVIW8lMXUpqGpK+rm/q+yAiIiJxYkAVkZqWzJRKpUZo1X9q0j5dmeI+iIiISNwYUEVE15KZQFlYNHblopq0T1emuA8iIiISNwZUkRF7yUyxt4+IiIjqPy6SIiIiIiJRYUAlIiIiIlFhQCUiIiIiUWFAJSIiIiJRYUAlIiIiIlFhQCUiIiIiUeE2U3WUkpKidV9QVkyimtLlfQVwH1kiImqYGFDrICUlBV5eXigoKNB6LCsmka5q+r6SyWQMqURE1KAwoNZBVlYWCgoKEBUVBS8vr2qPZU8X6UrX95VMJkNQUBCysrL43iIiogaFAVUPvLy84OPjY+pmUAPD9xURETVWXCRFRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREosKASkRERESiwoBKRERERKLCgEpEREREomJh6gYQNTYymaxO36/p8Xl5eTU6HxERkakxoBIZiVQqhZ2dHYKCgrQea2dnB6lUqrfzERER1ScMqERG4u7uDplMhqysLK3HSqVSuLu76+V858+fxxtvvFGjthIREZkSAyqREbm7u2sNnvo+H4f4iYiovuEiKSIiIiISFQZUIiIiIhIVBlQiIiIiEhUGVCIiIiISFQZUIiIiIhIVBlQiIiIiEhVuM0X1Rk0rLBn6PERERGQYDKgkeoaomKRLpSYiIiIyDQZUEr2aVGDSlS6VmoiIiMg0GFCpXtB3BSYiIiISLy6SIiIiIiJRYUAlIiIiIlFhQCUiIiIiUWFAJSIiIiJRYUAlIiIiIlFhQCUiIiIiUWl020ylpKTotJ8m98kkIiIiMo1GFVBTUlLg5eWFgoICrcfa2dlBJpMxpBIREREZWaMKqFlZWSgoKEBUVBS8vLyqPE4mkyEoKAhZWVkMqERERERG1qgCqoqXlxd8fHxM3QwiIiIiqgQXSRERERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoWJi6Afp08eJFODg4VPl9mUxmxNYQERERUW00qIDap08frcfY2dlBKpUaoTVEREREVBsNKqB+/fXXePbZZ6s9RiqVwt3d3UgtIiIiIqKaalAB9cknn4SPj4+pm0FEREREdcBFUkREREQkKgyoRERERCQqDKhEREREJCoMqEREREQkKgyoRERERCQqDKhEREREJCoMqEREREQkKg1qH1R901YalaVTiYiIiPSPAbUSUqkUdnZ2CAoK0nosS6cSERER6RcDaiXc3d0hk8mQlZWl9ViWTiUiIiLSLwbUKri7uzN4EhEREZkAF0kRERERkagwoBIRERGRqDCgEhEREZGoMKASERERkagwoBIRERGRqDCgEhEREZGoNIhtpkpLSwEA+fn5yMnJMXFriMQlPz8fwH+fEyIiIrFrEAE1NzcXADB06FATt4RIvHJzc+Hs7GzqZhAREWklKW0A3SpKpRIZGRlwdHSERCKp8c/n5OSgdevWSE1NhZOTkwFaWHdso340xjaWlpYiNzcXbm5uMDPjrB4iIhK/BtGDamZmhlatWtX5PE5OTqINLSpso340tjay55SIiOoTdqcQERERkagwoBIRERGRqDCgArC2tsaHH34Ia2trUzelSmyjfrCNRERE4tcgFkkRERERUcPBHlQiIiIiEhUGVCIiIiISlQaxzVRd90Elash03QeVnyOiqnE/YSLjahABNSMjA61btzZ1M4hELTU1tdr9gvk5ItJO2+eIiPSjQQRUR0dHABB1dSBTefjwocbXEyZMwB9//IF169Zh+PDhwuP5+fmwt7fXer7yxwUGBuLkyZP4/PPPMXDgQNja2gqrzx977LEat69NmzYAgIEDByIiIkLje7qcTx9ee+017N69G66urhg2bBgiIyMBAG+++aZGz8nVq1dx/PhxeHp64vnnn8f27dsBAJmZmbCysjLY+WpKVZlK9TmpCj9HRFXT9XNERPrRIAKqajiyPlQHMjalUqnxtYVF2Utua2ur8VyZmZnBwcFB6/nKH6c6n5OTE9zd3TWO1eW1KN8+FUtLywo/b6zX1tLSEkDZvaoHQ2tra41Aqbr38sc5OTlpfK3v89WWtmF7fo6ItOP0FyLj4ESaRkpfu4txlzIiIiLSNwbURkbVS7dv3746h8u0tDScPXsWwH+9hHXVsWNHAECfPn30cr7aUD1HDx8+1HiOcnJyNI7Lzs4GUNbjeefOnSrPp3puMjMz8eDBA+Hx27dvC39XKpVITU0VznfhwoU63gUREVH9xYDayLz66quQSCSIjY3F/Pnzax1S09LS4O/vj8LCQnh4eKBv3756ad/SpUsRGBiI8ePH6+V8tfF///d/AIDCwkL88ssvwsKhw4cPC89XZmamECI9PDzwyy+/AADc3NwqDMfPmzcPZmZmKCkpwQ8//AAPDw8AwE8//YTk5GQolUocPnwYCQkJkEgkKC0txaVLlwAAnTp10svwPhERUX3CgNrIDB8+HOvWrYNEIsGm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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "z = samples['posterior'].stacked['z']\n", + "import corner\n", + "corner.corner(z)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "882c8a74", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "emri_few_timm", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/02_bimodal/config_amortized.yml b/examples/02_bimodal/config_amortized.yml index 45a1249..d725ce9 100644 --- a/examples/02_bimodal/config_amortized.yml +++ b/examples/02_bimodal/config_amortized.yml @@ -43,29 +43,25 @@ graph: - ['uniform', -10000.0, 10000.0] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true - norm_momentum: 0.003 - use_log_update: true - adaptive_momentum: false + max_epochs: 300 + net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf + lr: 0.01 + gamma: 0.5 embedding: _target_: model.E _input_: [x] - optimizer: - lr: 0.01 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: false - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + norm_momentum: 0.003 + use_log_update: true + adaptive_momentum: false + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 1 diff --git a/examples/02_bimodal/config_regular.yml b/examples/02_bimodal/config_regular.yml index c37c7fa..417fac1 100644 --- a/examples/02_bimodal/config_regular.yml +++ b/examples/02_bimodal/config_regular.yml @@ -43,29 +43,25 @@ graph: - ['uniform', -10000.0, 10000.0] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true - norm_momentum: 0.003 - use_log_update: true - adaptive_momentum: false + max_epochs: 300 + net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf + lr: 0.01 + gamma: 0.5 embedding: _target_: model.E _input_: [x] - optimizer: - lr: 0.01 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: true - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + norm_momentum: 0.003 + use_log_update: true + adaptive_momentum: false + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: true + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 1 diff --git a/examples/02_bimodal/config_rounds_fill.yml b/examples/02_bimodal/config_rounds_fill.yml index f8c4794..4feca28 100644 --- a/examples/02_bimodal/config_rounds_fill.yml +++ b/examples/02_bimodal/config_rounds_fill.yml @@ -43,29 +43,25 @@ graph: - ['uniform', -10000.0, 10000.0] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true - norm_momentum: 0.0005 - use_log_update: true - adaptive_momentum: false + max_epochs: 300 + net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf + lr: 0.003 + gamma: 0.5 embedding: _target_: model.E _input_: [x] - optimizer: - lr: 0.003 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: false - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + norm_momentum: 0.0005 + use_log_update: true + adaptive_momentum: false + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 1 diff --git a/examples/02_bimodal/config_rounds_renew.yml b/examples/02_bimodal/config_rounds_renew.yml index 521f488..97a56a6 100644 --- a/examples/02_bimodal/config_rounds_renew.yml +++ b/examples/02_bimodal/config_rounds_renew.yml @@ -43,29 +43,25 @@ graph: - ['uniform', -10000.0, 10000.0] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 600 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true - norm_momentum: 0.002 - use_log_update: true - adaptive_momentum: false + max_epochs: 600 + net_type: nsf # zuko_gf, maf, nice, sospf, naf, gf + lr: 0.006 + gamma: 0.5 embedding: _target_: model.E _input_: [x] - optimizer: - lr: 0.006 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: false - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + norm_momentum: 0.002 + use_log_update: true + adaptive_momentum: false + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 1 diff --git a/examples/03_composite/config.yml b/examples/03_composite/config.yml index ec1be6b..d362235 100644 --- a/examples/03_composite/config.yml +++ b/examples/03_composite/config.yml @@ -45,13 +45,10 @@ graph: - ['uniform', -1.0, 1.0] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: zuko_gf # nsf, zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true + max_epochs: 300 + net_type: zuko_gf # nsf, zuko_gf, maf, nice, sospf, naf, gf + lr: 0.001 + gamma: 0.5 embedding: _target_: model.Concatenate _input_: @@ -66,24 +63,23 @@ graph: - _target_: model.Linear out_features: 512 _input_: [x] - optimizer: - lr: 0.001 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: false - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 0.5 z2: evidence: [x] - simulator: + simulator: _target_: falcon.priors.Hypercube - priors: + priors: - ['uniform', -1.2, 1.2] - ['uniform', -1.2, 1.2] - ['uniform', -1.2, 1.2] @@ -91,13 +87,10 @@ graph: - ['uniform', -1.2, 1.2] estimator: _target_: falcon.estimators.Flow - loop: - max_epochs: 300 - batch_size: 128 - early_stop_patience: 32 - network: - net_type: nsf # nsf, zuko_gf, maf, nice, sospf, naf, gf - theta_norm: true + max_epochs: 300 + net_type: nsf # nsf, zuko_gf, maf, nice, sospf, naf, gf + lr: 0.001 + gamma: 0.5 embedding: _target_: timm.create_model model_name: resnet18 #convnext_tiny @@ -107,16 +100,15 @@ graph: _input_: _target_: model.Unsqueeze _input_: [x] - optimizer: - lr: 0.001 - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.5 - discard_samples: false - log_ratio_threshold: -20 - sample_reference_posterior: false - use_best_models_during_inference: true + batch_size: 128 + early_stop_patience: 32 + theta_norm: true + lr_decay_factor: 0.5 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20 + sample_reference_posterior: false + use_best_models: true ray: num_gpus: 0.5 diff --git a/examples/04_gaussian/config.yml b/examples/04_gaussian/config.yml index 20b5c0a..49decd6 100644 --- a/examples/04_gaussian/config.yml +++ b/examples/04_gaussian/config.yml @@ -69,26 +69,22 @@ graph: estimator: _target_: falcon.estimators.GaussianFullCov - loop: - max_epochs: 8000 - batch_size: 128 - early_stop_patience: 128 - network: - hidden_dim: 128 # MLP hidden layer dimension - num_layers: 3 # Number of hidden layers - momentum: 0.01 # EMA momentum for running statistics - min_var: 1.0e-20 # Minimum variance for numerical stability - eig_update_freq: 1 # Eigendecomposition update frequency + max_epochs: 8000 + lr: 0.01 + gamma: 0.1 # Tempering for proposal sampling embedding: _target_: model.E_identity _input_: [x] - optimizer: - lr: 0.01 - lr_decay_factor: 1.0 # 1.0 = no LR decay - inference: - gamma: 0.1 # Tempering for proposal sampling - discard_samples: false - log_ratio_threshold: -20.0 + batch_size: 128 + early_stop_patience: 128 + hidden_dim: 128 # MLP hidden layer dimension + num_layers: 3 # Number of hidden layers + momentum: 0.01 # EMA momentum for running statistics + min_var: 1.0e-20 # Minimum variance for numerical stability + eig_update_freq: 1 # Eigendecomposition update frequency + lr_decay_factor: 1.0 # 1.0 = no LR decay + discard_samples: false + log_ratio_threshold: -20.0 ray: num_gpus: 1 diff --git a/examples/04_gaussian/notebook.ipynb b/examples/04_gaussian/notebook.ipynb new file mode 100644 index 0000000..93f6d79 --- /dev/null +++ b/examples/04_gaussian/notebook.ipynb @@ -0,0 +1,377 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "65ae874c", + "metadata": {}, + "source": [ + "# 04 — Gaussian posterior: programmatic graph API\n", + "\n", + "This notebook demonstrates the **Python-first** way to define a Falcon model:\n", + "build the graph with `Graph.add_node()` instead of a YAML config file.\n", + "The forward model and embedding are plain Python callables defined right here\n", + "in the notebook — no separate `src/model.py` needed.\n", + "\n", + "The same model is also runnable via the CLI:\n", + "```bash\n", + "cd examples/04_gaussian\n", + "falcon launch -o output/cli_run\n", + "```\n", + "That CLI path uses `config.yml` + `src/model.py`. Both paths produce\n", + "identical results; the notebook path is the \"define your own\" lesson.\n", + "\n", + "**Prerequisites**: run `python data/gen_mock_data.py` once to create the\n", + "mock observation, then execute this notebook from `examples/04_gaussian/`." + ] + }, + { + "cell_type": "markdown", + "id": "ad262282", + "metadata": {}, + "source": [ + "## 1. Define the forward model and embedding in Python" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "09925d35", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "import falcon\n", + "\n", + "\n", + "class ExpPlusNoise:\n", + " \"\"\"Forward model: x = exp(z) + noise. Plain callable, no base class needed.\"\"\"\n", + "\n", + " def __init__(self, sigma: float = 1e-6):\n", + " self.sigma = sigma\n", + "\n", + " def simulate_batch(self, batch_size, z):\n", + " z = torch.tensor(z)\n", + " x = torch.exp(z) + torch.randn_like(z) * self.sigma\n", + " return x.numpy()\n", + "\n", + "\n", + "class IdentityEmbedding(nn.Module):\n", + " \"\"\"Pass-through embedding: observation x is fed directly to the network.\"\"\"\n", + "\n", + " def forward(self, inputs: dict) -> torch.Tensor:\n", + " return inputs[\"x\"]" + ] + }, + { + "cell_type": "markdown", + "id": "ba177816", + "metadata": {}, + "source": [ + "## 2. Load the observation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "308371e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Observation shape: (3,) values: [6.73794700e-03 1.00000000e+00 1.48413159e+02]\n" + ] + } + ], + "source": [ + "obs = np.load(\"data/mock_data.npz\")[\"x\"] # shape (3,)\n", + "print(\"Observation shape:\", obs.shape, \" values:\", obs)" + ] + }, + { + "cell_type": "markdown", + "id": "4264809c", + "metadata": {}, + "source": [ + "## 3. Build the graph programmatically\n", + "\n", + "`falcon.Graph()` starts empty. `add_node()` accepts live Python objects\n", + "for `simulator=` and `estimator=`; they are shipped to Ray actors via\n", + "cloudpickle — no importable path required." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "43999ff3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "
\n",
+       "flowchart LR\n",
+       "    z[\"z
sim:Product
est:GaussianFullCov
\"]\n", + " style z fill:#d6eaf8,stroke:#2980b9\n", + " x[\"x
ExpPlusNoise · observed\"]\n", + " style x fill:#d5f5e3,stroke:#27ae60\n", + " z --> x\n", + " x -.->|evidence| z\n", + "
\n", + "
\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph = falcon.Graph()\n", + "\n", + "graph.add_node(\n", + " \"z\",\n", + " simulator=falcon.priors.Product([\n", + " [\"normal\", 0.0, 0.1],\n", + " [\"normal\", 0.0, 1.0],\n", + " [\"normal\", 0.0, 10.0],\n", + " ]),\n", + " estimator=falcon.estimators.GaussianFullCov(\n", + " max_epochs=8000,\n", + " lr=0.01,\n", + " gamma=0.1,\n", + " embedding=None, # pass-through: equivalent to E_identity\n", + " batch_size=128,\n", + " early_stop_patience=128,\n", + " hidden_dim=128,\n", + " num_layers=3,\n", + " momentum=0.01,\n", + " min_var=1e-20,\n", + " eig_update_freq=1,\n", + " lr_decay_factor=1.0,\n", + " discard_samples=False,\n", + " log_ratio_threshold=-20.0,\n", + " ),\n", + " evidence=[\"x\"],\n", + " ray_num_gpus=0.5,\n", + ")\n", + "\n", + "graph.add_node(\n", + " \"x\",\n", + " simulator=ExpPlusNoise(sigma=1e-6),\n", + " parents=[\"z\"],\n", + " observed=obs,\n", + " ray_num_gpus=0.5,\n", + ")\n", + "\n", + "graph" + ] + }, + { + "cell_type": "markdown", + "id": "181817c8", + "metadata": {}, + "source": [ + "## 4. Launch training\n", + "\n", + "`falcon.launch(graph)` synthesises a default config (buffer, paths, logging),\n", + "saves it as `config.yml` in the output directory, and runs training.\n", + "Pass `overrides=` to customise buffer size or epoch count." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28ff8ef9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-08T23:01:39 [INFO] falcon v0.4.3.dev26+g518dce194.d20260608\n", + "2026-06-08T23:01:39 [INFO] Output: output/260608-230139-gifted-spaniel\n", + "2026-06-08T23:01:39 [INFO] Ray: 145.136.62.31:56908 (new local instance)\n", + "2026-06-08T23:01:39 [INFO] Resources: 72 CPU, 1 GPU, 319.0 GB\n", + "2026-06-08T23:01:39 [INFO] Falcon graph structure:\n", + " Node name List of parents Class name\n", + "* z <- | \n", + "* x <- z | <__main__.ExpPlusNoise object at 0x1526a3bb6080>\n", + "\n", + "2026-06-08T23:01:39 [INFO] Observed: x [1, 3]\n", + "2026-06-08T23:01:39 [WARNING] Could not create monitor bridge: The name falcon:monitor_bridge (namespace=None) is already taken. Please use a different name or get the existing actor using ray.get_actor('falcon:monitor_bridge', namespace='None')\n", + "2026-06-08T23:01:39 [INFO] Spinning up graph...\n" + ] + } + ], + "source": [ + "run = falcon.launch(\n", + " graph,\n", + " #output=\"output/notebook_run\",\n", + " overrides=[\n", + " \"buffer.min_samples=1024\",\n", + " \"buffer.max_samples=1024\",\n", + " \"buffer.validation_samples=256\",\n", + " \"buffer.simulate_count=128\",\n", + " \"buffer.simulate_when_full=true\",\n", + " \"buffer.simulate_interval=0.001\",\n", + " \"buffer.snapshot_every=10\",\n", + " \"sample.posterior.n=1000\",\n", + " ],\n", + ")\n", + "run" + ] + }, + { + "cell_type": "markdown", + "id": "e4782c80", + "metadata": {}, + "source": [ + "## 5. Inspect the saved config\n", + "\n", + "The saved `config.yml` is human-readable; live Python objects appear as\n", + "`` placeholders so the file is honest about\n", + "reproducibility." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6aa99746", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "logging:\n", + " local:\n", + " enabled: true\n", + " dir: output/260608-225713-diamond-binturong/graph\n", + "paths:\n", + " imports: []\n", + " graph: output/260608-225713-diamond-binturong/graph\n", + " samples: output/260608-225713-diamond-binturong/samples\n", + "buffer:\n", + " min_samples: 1024\n", + " max_samples: 1024\n", + " validation_samples: 256\n", + " simulate_count: 128\n", + " simulate_when_full: true\n", + " simulate_interval: 0.001\n", + " snapshot_every: 10\n", + "sample:\n", + " posterior:\n", + " 'n': 1000\n", + "graph:\n", + " z:\n", + " evidence:\n", + " - x\n", + " simulator: ''\n", + " estimator: ''\n", + " ray:\n", + " num_gpus: 0.5\n", + " x:\n", + " parents:\n", + " - z\n", + " simulator: ''\n", + " observed: ''\n", + " ray:\n", + " num_gpus: 0.5\n", + "run_dir: output/260608-225713-diamond-binturong\n", + "\n" + ] + } + ], + "source": [ + "cfg_path = run.run_dir / \"config.yml\"\n", + "print(cfg_path.read_text())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e454047e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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QYMGYeXKlRg0aBC2b9+OJ598Em1tbXjppZcAAL/73e/Q2tqKuro6vPLKKwCAQYMGYezYsXjmmWfw5JNP4qGHHsL1118PANL/0Nu3b8e8efOQnJyMp556ClqtFjk5Objhhhvw73//GykpKbL23nnnnRg9ejSef/55iKJo9WexZMkShIaG4umnn8bhw4exadMmHDt2TCrcoKajowOzZs1CRUUFlixZgoSEBHz44YdIT09HS0sLHnvsMURGRmLTpk149NFHMX/+fNx+++0AgGuuucZsW06ePIlp06ahvb0dy5YtQ0REBN5++23ccsst+OijjzB//nzMmDEDW7Zswb333osbb7zR5H9Wpd/97nd48MEHZdfeeecdfPXVV6r/iBqrr6/HqVOnMHHiRJPnUlJS8MUXX1h8vTm//OUv8dvf/hYffPABHn/8cdlzH3zwAW666SaEhYXh/PnzmDNnDrq6urB06VIMHz4c9fX1+Oyzz9DS0oKQkBC7P7uiogJ33HEHHnjgASxatAhvvfUW0tPTkZycjCuvvBJAdzBPTU2FTqfDmjVrMHDgQLz++uuqnfAtW7Zg0aJFmDNnDl588UW0t7dj06ZNuO6663DgwAHEx8fjhhtuwG9+8xusX78et912GyZMmIATJ05g6dKlSEtLwyOPPNKjnyM5B+PjJYyPrnfzzTdj0KBB+OCDDzBz5kzZc++//z6uvPJK6fdywYIF+O9//4ulS5ciPj4ep06dwjfffIOamhrEx8fb/dnNzc2YO3cubr/9dvzyl7/ERx99hNWrV+Pqq6/GvHnzAHT//c6ePRs1NTVYtmwZoqKisGXLFmzfvt3k/Wz5/Rw7diyeffZZPP7447jjjjtwyy234Ny5c0hPT8fll1+OZ555xv4fIjkVY+QljJH2a2hoAAAMGTKkR69njPSwGCmSQ3z99deij4+P6OPjI06dOlX87W9/K3711Vfi+fPnZfeVl5eLWq1WnD9/vqjX62XPCYIg/bm9vd3kMx5++GExKChI7OzslK7dfPPNYlxcnMm9e/fuFQGIOTk5Jp8xevRocc6cOSafl5CQIN54443StaeeekoEIN599902/QxycnJEAGJycrLs+/7Tn/4kAhA//fRT6drMmTPFmTNnSo83bNggAhDfeecd6dr58+fFqVOnioMGDRLb2tpEURTFn376SQQgPvXUUza1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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "z = run.buffer.stacked['z.value']\n", + "import pylab as plt\n", + "# Plot scatter plot as function of index for all three dimensions\n", + "plt.figure(figsize=(12, 4))\n", + "for i in range(3):\n", + " plt.subplot(1, 3, i + 1)\n", + " plt.scatter(range(len(z)), z[:, i], s=5)\n", + " plt.xlabel(\"Index\")\n", + " plt.ylabel(f\"z[{i}]\")\n", + " plt.title(f\"Scatter plot of z[{i}] vs Index\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "635bc836", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "emri_few_timm", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/05_linear_regression/config.yml b/examples/05_linear_regression/config.yml index b99ef63..afacbe2 100644 --- a/examples/05_linear_regression/config.yml +++ b/examples/05_linear_regression/config.yml @@ -2,8 +2,8 @@ # Falcon Example Configuration: 05_linear_regression # ----------------------------------------------------------------------------- # Linear regression with sinusoidal basis functions: -# y = Phi @ theta + noise -# Phi[i, k] = sin((k+1) * x_i), x in [0, 2*pi), 20000 bins, 10 parameters +# x = Phi @ theta + noise, mu = Phi @ theta (noiseless signal) +# Phi[i, k] = sin((k+1) * t_i), t in [0, 2*pi), 1000 bins, 10 parameters # # Prior: theta ~ N(0, I) # Noise: N(0, sigma^2 * I), sigma = 0.1 @@ -46,21 +46,22 @@ paths: # Training buffer parameters # ----------------------------------------------------------------------------- buffer: - min_samples: 4096 - max_samples: 4096 + min_samples: 2048 + max_samples: 2048 validation_samples: 256 - simulate_count: 1024 + simulate_count: 128 simulate_chunk_size: 128 simulate_when_full: true - simulate_interval: 5 - snapshot_every: 0 + simulate_interval: 0.1 + snapshot_every: 10 # ----------------------------------------------------------------------------- # Graph definition using SNPE_gaussian estimator # ----------------------------------------------------------------------------- graph: theta: - evidence: [y] + evidence: [x] + scaffolds: [mu] simulator: _target_: falcon.priors.Product @@ -78,49 +79,54 @@ graph: estimator: _target_: falcon.estimators.GaussianFullCov - loop: - max_epochs: 1000 - batch_size: 128 - early_stop_patience: 32 - cache_sync_every: 1 - cache_on_device: false - network: - hidden_dim: 128 - num_layers: 3 - momentum: 0.1 - min_var: 1.0e-20 - eig_update_freq: 1 + max_epochs: 1000 + lr: 0.01 + gamma: 0.5 embedding: - _target_: model.E_fft_whiten - #_target_: model.E_fft_norm - _input_: [y] - n_bins: 1000000 - n_features: 128 - n_modes: 128 - optimizer: - lr: 0.001 - betas: [0.9, 0.9] - lr_decay_factor: 0.5 - scheduler_patience: 16 - inference: - gamma: 0.2 - discard_samples: false - log_ratio_threshold: -20.0 + _target_: falcon.embeddings.DynamicSVD + _input_: [x, mu] + n_components: 32 + momentum: 0.1 + batch_size: 128 + prior_epochs: 20 + early_stop_patience: 128 + hidden_dim: 128 + num_layers: 3 + momentum: 0.01 + + min_var: 1.0e-20 + eig_update_freq: 1 + + betas: [0.9, 0.9] + lr_decay_factor: 1 + lr_patience: 16 + discard_samples: false + log_ratio_threshold: -20.0 + cache_sync_every: 1 + cache_on_device: false sample_chunk_size: 128 ray: - num_gpus: 0.5 + num_gpus: 1 - y: + mu: parents: [theta] simulator: - _target_: model.LinearSimulator + _target_: model.SignalSimulator + n_bins: 1000 + sample_chunk_size: 128 + ray: + num_gpus: 0 + + x: + parents: [mu] + simulator: + _target_: model.NoiseSimulator sigma: 0.1 - n_bins: 1000000 - observed: "./data/mock_data.npz['y']" + observed: "./data/mock_data.npz['x']" sample_chunk_size: 128 ray: - num_gpus: 0.5 + num_gpus: 0 # ----------------------------------------------------------------------------- # Sampling configuration diff --git a/examples/05_linear_regression/data/gen_mock_data.py b/examples/05_linear_regression/data/gen_mock_data.py index ee3c73c..8f77175 100644 --- a/examples/05_linear_regression/data/gen_mock_data.py +++ b/examples/05_linear_regression/data/gen_mock_data.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """Generate mock data for the linear regression example. -Model: y = Phi @ theta + noise - - Phi[i, k] = sin((k+1) * x_i), 20000 bins, 10 parameters +Model: x = Phi @ theta + noise, mu = Phi @ theta (noiseless signal) + - Phi[i, k] = sin((k+1) * t_i), 1000 bins, 10 parameters - Prior: theta ~ N(0, I) - Noise: N(0, sigma^2 * I), sigma = 0.1 @@ -10,11 +10,11 @@ For a linear model with Gaussian prior and likelihood: Prior: theta ~ N(0, I) - Likelihood: y | theta ~ N(Phi @ theta, sigma^2 * I) - Posterior: theta | y ~ N(mu_post, Sigma_post) + Likelihood: x | theta ~ N(Phi @ theta, sigma^2 * I) + Posterior: theta | x ~ N(mu_post, Sigma_post) Sigma_post = (Phi^T Phi / sigma^2 + I)^{-1} - mu_post = Sigma_post @ Phi^T @ y / sigma^2 + mu_post = Sigma_post @ Phi^T @ x / sigma^2 Convention: Observations have no batch dimension - shape is [n_bins]. """ @@ -25,7 +25,7 @@ from model import design_matrix # Configuration -N_BINS = 1000000 +N_BINS = 1000 N_PARAMS = 10 SIGMA = 0.1 @@ -34,27 +34,24 @@ theta_true = np.random.randn(N_PARAMS) # Design matrix -Phi, x = design_matrix(N_BINS, N_PARAMS) +Phi, t = design_matrix(N_BINS, N_PARAMS) -# Observation: y = Phi @ theta (Asimov, no noise for clean testing) -y_obs = Phi @ theta_true +# Noiseless signal and noisy observation +mu = Phi @ theta_true +noise = np.random.randn(N_BINS) * SIGMA +x = mu + noise # Analytic posterior -# Sigma_post = (Phi^T Phi / sigma^2 + I)^{-1} PhiTPhi = Phi.T @ Phi precision_post = PhiTPhi / SIGMA**2 + np.eye(N_PARAMS) Sigma_post = np.linalg.inv(precision_post) - -# mu_post = Sigma_post @ Phi^T @ y / sigma^2 -mu_post = Sigma_post @ (Phi.T @ y_obs / SIGMA**2) - -# Marginal posterior widths +mu_post = Sigma_post @ (Phi.T @ x / SIGMA**2) marginal_std = np.sqrt(np.diag(Sigma_post)) # Print results -print(f"Linear regression: y = Phi @ theta + noise") +print(f"Linear regression: x = Phi @ theta + noise") print(f" {N_PARAMS} parameters, {N_BINS} bins, sigma = {SIGMA}") -print(f" Phi[i, k] = sin((k+1) * x_i), x in [0, 2*pi)") +print(f" Phi[i, k] = sin((k+1) * t_i), t in [0, 2*pi)") print() print(f"{'Param':<8} {'True':>10} {'Post Mean':>10} {'Post Std':>10}") @@ -62,15 +59,15 @@ for k in range(N_PARAMS): print(f"theta[{k}] {theta_true[k]:>10.4f} {mu_post[k]:>10.4f} {marginal_std[k]:>10.6f}") -print(f"\nPosterior correlation matrix (off-diagonal elements):") +print(f"\nPosterior correlation matrix:") corr = Sigma_post / np.outer(marginal_std, marginal_std) print(np.array2string(corr, precision=3, suppress_small=True)) # Save np.savez("mock_data.npz", - y=y_obs, # observation + x=x, theta_true=theta_true, mu_post=mu_post, Sigma_post=Sigma_post, marginal_std=marginal_std) -print(f"\nSaved to mock_data.npz (y_obs shape: {y_obs.shape})") +print(f"\nSaved to mock_data.npz (x shape: {mu.shape})") diff --git a/examples/05_linear_regression/data/mock_data.npz b/examples/05_linear_regression/data/mock_data.npz index a636799..237a412 100644 Binary files a/examples/05_linear_regression/data/mock_data.npz and b/examples/05_linear_regression/data/mock_data.npz differ diff --git a/examples/05_linear_regression/standalone.py b/examples/05_linear_regression/extras/standalone.py similarity index 97% rename from examples/05_linear_regression/standalone.py rename to examples/05_linear_regression/extras/standalone.py index 86dc832..d9c9308 100644 --- a/examples/05_linear_regression/standalone.py +++ b/examples/05_linear_regression/extras/standalone.py @@ -39,6 +39,7 @@ import torch import torch.nn as nn import numpy as np +from falcon.embeddings import DynamicSVD, DiagonalWhitener # Number of parameters (fixed) @@ -88,7 +89,7 @@ def get_config(): help='Disable input/output diagonal whitening') # Embedding parser.add_argument('--embedding', type=str, default='fft_norm', - choices=['none', 'linear', 'fft', 'fft_norm', 'gated'], + choices=['none', 'linear', 'fft', 'fft_norm', 'gated', 'svd'], help='Embedding mode: none, linear, fft, fft_norm, or gated') parser.add_argument('--n_features', type=int, default=128, help='Embedding output dimension (input to MLP)') @@ -320,6 +321,9 @@ def build_embedding(mode, n_bins, n_features, n_modes=0): return FFTNormEmbedding(n_bins, n_features, n_modes=n_modes), n_features elif mode == 'gated': return GatedEmbedding(n_bins, n_features), n_features + elif mode == 'svd': + whitener = DiagonalWhitener(dim=n_bins) + return DynamicSVD(n_components=n_features, buffer_size=256, whitener=whitener), n_features else: raise ValueError(f"Unknown embedding mode: {mode}") @@ -396,12 +400,15 @@ def _update_eigendecomp(self): def update_stats(self, z, x): """Update running mean and std. z=theta (D-dim), x=y (obs_dim-dim).""" with torch.no_grad(): - self.input_mean.lerp_(x.mean(dim=0), self.momentum) - self.output_mean.lerp_(z.mean(dim=0), self.momentum) + if self.embedding_mode == 'svd': + self.embedding.update(x) + else: + self.input_mean.lerp_(x.mean(dim=0), self.momentum) + input_var = x.var(dim=0).clamp(min=self.min_var) + self.input_std.lerp_(input_var.sqrt(), self.momentum) - input_var = x.var(dim=0).clamp(min=self.min_var) + self.output_mean.lerp_(z.mean(dim=0), self.momentum) output_var = z.var(dim=0).clamp(min=self.min_var) - self.input_std.lerp_(input_var.sqrt(), self.momentum) self.output_std.lerp_(output_var.sqrt(), self.momentum) def update_covariance(self, z, x): @@ -421,9 +428,10 @@ def update_covariance(self, z, x): def forward_mean(self, x): """Predict mean: whiten -> embed -> MLP -> de-whiten.""" - if self.no_whiten: + if self.no_whiten or self.embedding_mode == 'svd': h = self.embedding(x) - return self.net(h) + r = self.net(h) + return self.output_mean + self.output_std * r x_white = (x - self.input_mean) / self.input_std h = self.embedding(x_white) r = self.net(h) # (batch, D) diff --git a/examples/05_linear_regression/plot_posterior.py b/examples/05_linear_regression/plot_posterior.py deleted file mode 100644 index 8aac2c2..0000000 --- a/examples/05_linear_regression/plot_posterior.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -"""Plot posterior samples vs analytic posterior for the linear regression example.""" - -import numpy as np -import matplotlib.pyplot as plt -from pathlib import Path - -try: - import corner -except ImportError: - print("Install corner: pip install corner") - raise SystemExit(1) - -from falcon import load_run - - -def main(): - import argparse - - parser = argparse.ArgumentParser(description="Plot posterior comparison") - parser.add_argument( - "--run-dir", - default="output/latest", - help="Run directory (default: output/latest)", - ) - parser.add_argument( - "--data", - default="data/mock_data.npz", - help="Path to mock data with analytic posterior", - ) - parser.add_argument( - "--output", - default=None, - help="Output file (default: {run-dir}/posterior_corner.png)", - ) - args = parser.parse_args() - - # Load falcon posterior samples - run = load_run(args.run_dir) - samples = run.samples.posterior.stacked["theta"] - print(f"Loaded {len(samples)} posterior samples, shape: {samples.shape}") - - n_params = samples.shape[1] - labels = [f"$\\theta_{{{i}}}$" for i in range(n_params)] - - # Load analytic posterior - data = np.load(args.data) - theta_true = data["theta_true"] - mu_post = data["mu_post"] - Sigma_post = data["Sigma_post"] - - # Draw samples from the analytic posterior for corner plot - analytic_samples = np.random.multivariate_normal(mu_post, Sigma_post, size=len(samples)) - - # Plot: analytic posterior in blue, falcon samples in red - fig = corner.corner( - analytic_samples, - labels=labels, - color="C0", - hist_kwargs={"density": True}, - plot_datapoints=False, - levels=(0.68, 0.95), - ) - corner.corner( - samples, - fig=fig, - color="C1", - hist_kwargs={"density": True}, - plot_datapoints=False, - levels=(0.68, 0.95), - ) - - # Add ground truth lines - corner.overplot_lines(fig, theta_true, color="k", linestyle="--", linewidth=1) - - # Legend - fig.legend( - handles=[ - plt.Line2D([], [], color="C0", label="Analytic"), - plt.Line2D([], [], color="C1", label="Falcon"), - plt.Line2D([], [], color="k", linestyle="--", label="Truth"), - ], - loc="upper right", - fontsize=12, - ) - - output = args.output or str(Path(args.run_dir) / "posterior_corner.png") - fig.savefig(output, dpi=150, bbox_inches="tight") - print(f"Saved: {output}") - - -if __name__ == "__main__": - main() diff --git a/examples/05_linear_regression/run.py b/examples/05_linear_regression/run.py new file mode 100755 index 0000000..de8ff32 --- /dev/null +++ b/examples/05_linear_regression/run.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Run the linear regression example and validate against the analytic posterior. + +Steps: +1. Generate mock observation data (if needed) +2. Run falcon launch (trains model and auto-generates posterior samples) +3. Print posterior mean/std vs analytic solution +4. Save corner plot, buffer std plot, and loss plot to the run directory + +Model: y = Phi @ theta + noise + - Phi[i, k] = sin((k+1) * x_i), x in [0, 2*pi), 1000 bins, 10 parameters + - Prior: theta ~ N(0, I) + - Noise: N(0, 0.1^2 * I) + - Analytic posterior: theta | y ~ N(mu_post, Sigma_post) +""" + +import subprocess +import sys +from pathlib import Path +import argparse + +import numpy as np +import matplotlib.pyplot as plt + + +def parse_args(): + parser = argparse.ArgumentParser(description="Run the 05_linear_regression falcon example.") + parser.add_argument("-o", "--output", default="output/run", help="Output directory (default: output/run)") + parser.add_argument("-c", "--config", default=None, help="Config file (default: config.yml)") + return parser.parse_args() + + +def run_command(cmd, description, cwd=None): + print(f"\n{'='*60}\n{description}\n{'='*60}") + print(f"Running: {' '.join(cmd)}\n") + result = subprocess.run(cmd, cwd=cwd) + if result.returncode != 0: + print(f"Command failed with return code {result.returncode}") + return False + return True + + +def generate_mock_data(script_dir): + mock_data_path = script_dir / "data" / "mock_data.npz" + if mock_data_path.exists(): + if "x" in np.load(mock_data_path): + print(f"Mock data already exists: {mock_data_path}") + return True + print("Mock data is stale (missing key 'x'), regenerating...") + return run_command(["python", "gen_mock_data.py"], "Generating mock data", cwd=script_dir / "data") + + +def print_posterior_stats(run_dir, script_dir): + from falcon import load_run + run = load_run(run_dir) + samples = run.samples.posterior.stacked["theta"] + + data = np.load(script_dir / "data/mock_data.npz") + theta_true = data["theta_true"] + mu_post = data["mu_post"] + marginal_std = data["marginal_std"] + + means = samples.mean(axis=0) + stds = samples.std(axis=0) + n_params = samples.shape[1] + + print(f"\n{'='*60}\nPosterior statistics (n={len(samples)} samples)\n{'='*60}") + print(f"{'Param':<10} {'True':>8} {'Analytic':>10} {'Inferred':>10} {'Anal Std':>10} {'Inf Std':>10} {'Ratio':>8}") + print("-" * 68) + for k in range(n_params): + print(f"theta[{k}] {theta_true[k]:>8.4f} {mu_post[k]:>10.4f} {means[k]:>10.4f} " + f"{marginal_std[k]:>10.6f} {stds[k]:>10.6f} {stds[k]/marginal_std[k]:>8.3f}") + + mean_error = np.sqrt(np.mean((means - mu_post)**2)) + std_ratio = stds / marginal_std + print(f"\nRMSE(mean_inferred - mean_analytic): {mean_error:.6f}") + print(f"Std ratio (inferred/analytic): {std_ratio.mean():.4f} +/- {std_ratio.std():.4f}") + + return samples + + +def plot_corner(samples, script_dir, out_path): + import corner + + data = np.load(script_dir / "data/mock_data.npz") + theta_true = data["theta_true"] + mu_post = data["mu_post"] + Sigma_post = data["Sigma_post"] + + labels = [f"$\\theta_{k}$" for k in range(samples.shape[1])] + analytic_samples = np.random.multivariate_normal(mu_post, Sigma_post, size=len(samples)) + + fig = corner.corner(analytic_samples, labels=labels, color="C0", + plot_datapoints=False, levels=(0.68, 0.95), + hist_kwargs={"density": True}) + corner.corner(samples, fig=fig, color="C1", + plot_datapoints=False, levels=(0.68, 0.95), + hist_kwargs={"density": True}) + corner.overplot_lines(fig, theta_true, color="k", linestyle="--", linewidth=1) + + fig.legend(handles=[ + plt.Line2D([], [], color="C0", label="Analytic"), + plt.Line2D([], [], color="C1", label="Falcon"), + plt.Line2D([], [], color="k", ls="--", label="Truth"), + ], loc="upper right", fontsize=10) + + fig.savefig(out_path, dpi=150, bbox_inches="tight") + plt.close() + print(f"Saved: {out_path}") + + +def plot_buffer_mean(run_dir, out_path, window=50): + files = sorted((run_dir / "buffer/snapshots").glob("*.npz")) + if not files: + print("No buffer snapshots found.") + return + theta = np.stack([np.load(f)["theta.value"] for f in files]) + n, d = theta.shape + cmap = plt.get_cmap("tab10") + + fig, ax = plt.subplots(figsize=(12, 4)) + for i in range(d): + means = [theta[max(0, j - window):j, i].mean() for j in range(window, n)] + ax.plot(range(window, n), means, color=cmap(i % 10), alpha=0.7, lw=0.8, label=f"$\\theta_{i}$") + ax.axhline(0, color="k", lw=0.5, ls="--") + ax.set_xlabel("Sample index") + ax.set_ylabel(f"Rolling mean (window={window})") + ax.set_title("Rolling mean of buffer samples per parameter") + ax.legend(ncol=5, fontsize=7) + plt.tight_layout() + plt.savefig(out_path, dpi=150) + plt.close() + print(f"Saved: {out_path}") + + +def plot_buffer(run_dir, out_scatter, out_std, window=50): + files = sorted((run_dir / "buffer/snapshots").glob("*.npz")) + if not files: + print("No buffer snapshots found.") + return + theta = np.stack([np.load(f)["theta.value"] for f in files]) + n, d = theta.shape + cmap = plt.get_cmap("tab10") + + fig, ax = plt.subplots(figsize=(12, 4)) + for i in range(d): + ax.scatter(range(n), theta[:, i], s=2, alpha=0.4, color=cmap(i % 10), label=f"$\\theta_{i}$") + ax.set_xlabel("Sample index") + ax.set_ylabel("Parameter value") + ax.set_title(f"Buffer samples over time (n={n})") + ax.legend(ncol=5, fontsize=7) + plt.tight_layout() + plt.savefig(out_scatter, dpi=150) + plt.close() + print(f"Saved: {out_scatter}") + + fig, ax = plt.subplots(figsize=(12, 4)) + for i in range(d): + stds = [theta[max(0, j - window):j, i].std() for j in range(window, n)] + ax.plot(range(window, n), np.log10(np.array(stds) + 1e-10), + color=cmap(i % 10), alpha=0.7, label=f"$\\theta_{i}$") + ax.set_xlabel("Sample index") + ax.set_ylabel(f"log10 std (rolling window={window})") + ax.set_title("Rolling std of buffer samples") + ax.legend(ncol=5, fontsize=7) + plt.tight_layout() + plt.savefig(out_std, dpi=150) + plt.close() + print(f"Saved: {out_std}") + + +def plot_loss(run_dir, out_path): + base = run_dir / "graph/theta/metrics" + + def load_metric(key): + chunks = sorted((base / key).glob("*.npz")) + if not chunks: + return None, None + steps = np.concatenate([np.load(c)["step"] for c in chunks]) + values = np.concatenate([np.load(c)["value"] for c in chunks]) + return steps, values + + train_steps, train_loss = load_metric("train/loss") + val_steps, val_loss = load_metric("val/loss") + if train_steps is None: + print("No loss metrics found.") + return + + fig, ax = plt.subplots(figsize=(10, 4)) + ax.plot(train_steps, train_loss, color="tab:blue", alpha=0.6, lw=0.8, label="train") + if val_steps is not None and len(val_steps) > 1 and len(train_steps) > 0: + scale = train_steps[-1] / val_steps[-1] + val_steps_scaled = val_steps * scale + else: + val_steps_scaled = val_steps + ax.plot(val_steps_scaled, val_loss, color="tab:orange", lw=1.5, label="val") + ax.set_xlabel("Step") + ax.set_ylabel("Loss") + ax.set_title("Train and validation loss") + ax.legend() + plt.tight_layout() + plt.savefig(out_path, dpi=150) + plt.close() + print(f"Saved: {out_path}") + + +def main(): + args = parse_args() + script_dir = Path(__file__).parent + run_dir = script_dir / args.output + + if not generate_mock_data(script_dir): + sys.exit(1) + + cmd = ["falcon", "launch", f"--output={args.output}"] + if args.config: + cmd += [f"--config={args.config}"] + if not run_command(cmd, "Running falcon launch (trains model + auto-samples posterior)", cwd=script_dir): + sys.exit(1) + + samples = print_posterior_stats(run_dir, script_dir) + if samples is None: + sys.exit(1) + + plot_corner(samples, script_dir, run_dir / "corner.png") + plot_buffer_mean(run_dir, run_dir / "buffer_mean.png") + plot_buffer(run_dir, run_dir / "buffer_scatter.png", run_dir / "buffer_std.png") + plot_loss(run_dir, run_dir / "loss.png") + + +if __name__ == "__main__": + main() diff --git a/examples/05_linear_regression/run_example.py b/examples/05_linear_regression/run_example.py deleted file mode 100644 index 6d78315..0000000 --- a/examples/05_linear_regression/run_example.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 -"""Run the linear regression example and compare with analytic posterior. - -This script: -1. Generates mock observation data (if needed) -2. Runs falcon launch to train the model -3. Generates posterior samples -4. Compares inferred posterior with analytic solution - -Model: y = Phi @ theta + noise - - Phi[i, k] = sin((k+1) * x_i), 100 bins, 10 parameters - - Prior: theta ~ N(0, I) - - Noise: N(0, 0.1^2 * I) -""" - -import subprocess -import sys -from pathlib import Path -import numpy as np - -# Configuration -RUN_DIR = "output/run" - - -def run_command(cmd: list[str], description: str, cwd: Path = None) -> bool: - """Run a command and return success status.""" - print(f"\n{'='*60}") - print(f"{description}") - print(f"{'='*60}") - print(f"Running: {' '.join(cmd)}\n") - - result = subprocess.run(cmd, cwd=cwd) - if result.returncode != 0: - print(f"Command failed with return code {result.returncode}") - return False - return True - - -def generate_mock_data(script_dir: Path) -> bool: - """Generate mock data if it doesn't exist.""" - mock_data_path = script_dir / "data" / "mock_data.npz" - if mock_data_path.exists(): - print(f"Mock data already exists: {mock_data_path}") - return True - - return run_command( - ["python", "gen_mock_data.py"], - "Generating mock observation data", - cwd=script_dir / "data" - ) - - -def analyze_samples(samples_dir: Path, script_dir: Path) -> None: - """Load posterior samples and compare with analytic posterior.""" - print(f"\n{'='*60}") - print("Comparing inferred vs analytic posterior") - print(f"{'='*60}") - - # Load analytic posterior - mock_data = np.load(script_dir / "data" / "mock_data.npz") - theta_true = mock_data["theta_true"] - mu_post_analytic = mock_data["mu_post"] - Sigma_post_analytic = mock_data["Sigma_post"] - marginal_std_analytic = mock_data["marginal_std"] - - # Find posterior samples - posterior_dir = samples_dir / "posterior" - if not posterior_dir.exists(): - print(f"No posterior samples found in {posterior_dir}") - return - - timestamp_dirs = sorted(posterior_dir.iterdir()) - if not timestamp_dirs: - print("No sample batches found") - return - - latest_dir = timestamp_dirs[-1] - print(f"Loading samples from: {latest_dir}") - - # Load all sample files - all_samples = [] - for npz_file in sorted(latest_dir.glob("*.npz")): - data = np.load(npz_file) - if 'theta' in data: - all_samples.append(data['theta']) - - if not all_samples: - print("No 'theta' samples found in npz files") - return - - samples = np.concatenate(all_samples, axis=0) if len(all_samples) > 1 else all_samples[0] - if samples.ndim == 1: - samples = samples.reshape(-1, 1) - - n_samples, n_params = samples.shape - print(f"Loaded {n_samples} samples with {n_params} parameters\n") - - # Inferred statistics - means_inferred = np.mean(samples, axis=0) - stds_inferred = np.std(samples, axis=0) - - # Print comparison table - print(f"{'Param':<10} {'True':>8} {'Analytic':>10} {'Inferred':>10} {'Anal Std':>10} {'Inf Std':>10}") - print("-" * 60) - for k in range(n_params): - print(f"theta[{k}] {theta_true[k]:>8.4f} {mu_post_analytic[k]:>10.4f} " - f"{means_inferred[k]:>10.4f} {marginal_std_analytic[k]:>10.6f} " - f"{stds_inferred[k]:>10.6f}") - - # Summary - mean_error = np.sqrt(np.mean((means_inferred - mu_post_analytic)**2)) - std_ratio = stds_inferred / marginal_std_analytic - print(f"\nRMSE(mean_inferred - mean_analytic): {mean_error:.6f}") - print(f"Std ratio (inferred/analytic): {std_ratio.mean():.4f} +/- {std_ratio.std():.4f}") - - -def main(): - script_dir = Path(__file__).parent - run_dir = script_dir / RUN_DIR - - # Step 0: Generate mock data if needed - if not generate_mock_data(script_dir): - sys.exit(1) - - # Step 1: Run falcon launch - if not run_command( - ["falcon", "launch", f"--output={RUN_DIR}"], - "Step 1: Running falcon launch", - cwd=script_dir - ): - sys.exit(1) - - # Step 2: Generate posterior samples - if not run_command( - ["falcon", "sample", "posterior", f"--output={RUN_DIR}"], - "Step 2: Generating posterior samples", - cwd=script_dir - ): - sys.exit(1) - - # Step 3: Analyze samples - samples_dir = run_dir / "samples" - analyze_samples(samples_dir, script_dir) - - -if __name__ == "__main__": - main() diff --git a/examples/05_linear_regression/src/model.py b/examples/05_linear_regression/src/model.py index 20f8bfc..a28087e 100644 --- a/examples/05_linear_regression/src/model.py +++ b/examples/05_linear_regression/src/model.py @@ -1,14 +1,14 @@ """ Model components for the linear regression example. -Linear model: y = Phi @ theta + noise - - Phi[i, k] = sin((k+1) * x_i), x_i on [0, 2*pi], 20000 bins, 10 parameters +Linear model: x = Phi @ theta + noise + - Phi[i, k] = sin((k+1) * t_i), t_i on [0, 2*pi], 1000 bins, 10 parameters - noise ~ N(0, sigma^2 * I) Components: - - LinearSimulator: Forward simulator (y = Phi @ theta + noise) - - E_fft_norm: FFT-based embedding with gated linear projection - - E_linear: Linear embedding network (kept for backwards compatibility) + - SignalSimulator: Noiseless forward model (mu = Phi @ theta) + - NoiseSimulator: Adds Gaussian noise (x = mu + noise) + - LinearSimulator: Combined simulator (kept for extras/standalone.py) """ import torch @@ -17,25 +17,46 @@ import falcon -def design_matrix(n_bins=20000, n_params=10): - """Build design matrix Phi[i, k] = sin((k+1) * x_i).""" - x = np.linspace(0, 2 * np.pi, n_bins, endpoint=False) - Phi = np.column_stack([np.sin((k + 1) * x) for k in range(n_params)]) - return Phi, x +def design_matrix(n_bins=1000, n_params=10): + """Build design matrix Phi[i, k] = sin((k+1) * t_i).""" + t = np.linspace(0, 2 * np.pi, n_bins, endpoint=False) + Phi = np.column_stack([np.sin((k + 1) * t) for k in range(n_params)]) + return Phi, t -class LinearSimulator: - """Linear regression simulator: y = Phi @ theta + noise. +class SignalSimulator: + """Noiseless forward model: mu = Phi @ theta.""" + + def __init__(self, n_bins: int = 1000, n_params: int = 10): + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + Phi, _ = design_matrix(n_bins, n_params) + self.Phi = torch.tensor(Phi, device=self.device) + + def simulate_batch(self, batch_size, theta): + theta = torch.tensor(theta, device=self.device) + Phi = self.Phi.to(dtype=theta.dtype) + return (theta @ Phi.T).cpu().numpy() + + +class NoiseSimulator: + """Adds Gaussian noise: x = mu + noise.""" + + def __init__(self, sigma: float = 0.1): + self.sigma = sigma + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + def simulate_batch(self, batch_size, mu): + mu = torch.tensor(mu, device=self.device) + noise = torch.randn_like(mu) * self.sigma + x = mu + noise + falcon.log({"x_mean": x.mean().item(), "x_std": x.std().item()}) + return x.cpu().numpy() - Args: - sigma: Standard deviation of observation noise per bin (default: 0.1) - n_bins: Number of data bins (default: 100) - n_params: Number of parameters (default: 10) - device: Auto-detected (uses CUDA if available). GPU accelerates the - large matrix multiply for high n_bins. - """ - def __init__(self, sigma: float = 0.1, n_bins: int = 20000, n_params: int = 10): +class LinearSimulator: + """Combined simulator: x = Phi @ theta + noise (kept for extras/standalone.py).""" + + def __init__(self, sigma: float = 0.1, n_bins: int = 1000, n_params: int = 10): self.sigma = sigma self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') Phi, _ = design_matrix(n_bins, n_params) @@ -44,24 +65,16 @@ def __init__(self, sigma: float = 0.1, n_bins: int = 20000, n_params: int = 10): def simulate_batch(self, batch_size, theta): theta = torch.tensor(theta, device=self.device) Phi = self.Phi.to(dtype=theta.dtype) - y = theta @ Phi.T + torch.randn(batch_size, Phi.shape[0], device=self.device, dtype=theta.dtype) * self.sigma - falcon.log({"y_mean": y.mean().item(), "y_std": y.std().item()}) - return y.cpu().numpy() + noise = torch.randn(batch_size, Phi.shape[0], device=self.device, dtype=theta.dtype) * self.sigma + x = theta @ Phi.T + noise + falcon.log({"x_mean": x.mean().item(), "x_std": x.std().item()}) + return x.cpu().numpy() class E_fft_norm(nn.Module): - """FFT-based embedding with gated linear projection. + """FFT-based embedding with gated linear projection.""" - Computes the orthonormal FFT of the input, truncates to n_modes, - stacks real and imaginary parts, and applies a gated linear layer. - - Args: - n_bins: Number of input bins (default: 20000) - n_features: Output embedding dimension (default: 128) - n_modes: Number of FFT modes to keep (default: 128) - """ - - def __init__(self, n_bins: int = 20000, n_features: int = 128, n_modes: int = 128): + def __init__(self, n_bins: int = 1000, n_features: int = 128, n_modes: int = 128): super().__init__() self.n_modes = n_modes self.linear = nn.Linear(n_modes * 2, n_features) @@ -69,54 +82,31 @@ def __init__(self, n_bins: int = 20000, n_features: int = 128, n_modes: int = 12 def forward(self, x): x = x.float() - # Orthonormal FFT, truncate to n_modes f = torch.fft.rfft(x, norm='ortho')[..., :self.n_modes] - # Stack real and imaginary parts h = torch.cat([f.real, f.imag], dim=-1) return self.linear(h) * torch.sigmoid(self.gate(h)) class E_fft_whiten(nn.Module): - """FFT embedding with built-in diagonal whitening on raw input. - - Matches the standalone.py pipeline: whiten(raw obs) -> ortho FFT -> linear. - This ensures the FFT operates on centered, standardized data. + """FFT embedding with diagonal whitening (EMA-updated) on raw input.""" - The whitening statistics are updated via EMA during training. - - Args: - n_bins: Number of input bins (default: 20000) - n_features: Output embedding dimension (default: 128) - n_modes: Number of FFT modes to keep (default: 128) - momentum: EMA momentum for whitening statistics (default: 0.01) - min_var: Minimum variance for numerical stability (default: 1e-20) - """ - - def __init__(self, n_bins: int = 20000, n_features: int = 128, n_modes: int = 128, + def __init__(self, n_bins: int = 1000, n_features: int = 128, n_modes: int = 128, momentum: float = 0.01, min_var: float = 1e-20): super().__init__() self.n_modes = n_modes self.momentum = momentum self.min_var = min_var - - # Whitening statistics on raw input self.register_buffer('_input_mean', torch.zeros(n_bins)) self.register_buffer('_input_std', torch.ones(n_bins)) - - # Single linear projection (matches standalone FFTNormEmbedding) self.linear = nn.Linear(n_modes * 2, n_features) def forward(self, x): x = x.float() - # Update whitening stats during training if self.training: with torch.no_grad(): m = self.momentum self._input_mean = (1 - m) * self._input_mean + m * x.mean(dim=0) - batch_var = x.var(dim=0).clamp(min=self.min_var) - self._input_std = (1 - m) * self._input_std + m * batch_var.sqrt() - - # Whiten, then FFT + self._input_std = (1 - m) * self._input_std + m * x.var(dim=0).clamp(min=self.min_var).sqrt() x_white = (x - self._input_mean.detach()) / self._input_std.detach() f = torch.fft.rfft(x_white, norm='ortho')[..., :self.n_modes] h = torch.cat([f.real, f.imag], dim=-1) @@ -124,14 +114,9 @@ def forward(self, x): class E_linear(nn.Module): - """Linear embedding: projects data down to a lower-dimensional summary. - - Args: - n_bins: Number of input bins (default: 20000) - n_out: Output embedding dimension (default: 20) - """ + """Linear embedding: projects data down to a lower-dimensional summary.""" - def __init__(self, n_bins: int = 20000, n_out: int = 20): + def __init__(self, n_bins: int = 1000, n_out: int = 20): super().__init__() self.linear = nn.Linear(n_bins, n_out) diff --git a/falcon/__init__.py b/falcon/__init__.py index ac4eca4..3219932 100644 --- a/falcon/__init__.py +++ b/falcon/__init__.py @@ -16,6 +16,10 @@ "read_run", "load_run", "read_samples", + "config", + "init", + "launch", + "shutdown", "estimators", "priors", "embeddings", @@ -41,6 +45,10 @@ "read_run": ".core.run_reader", "load_run": ".core.run_loader", "read_samples": ".core.samples_reader", + "config": ".api", + "init": ".api", + "launch": ".api", + "shutdown": ".api", } diff --git a/falcon/api.py b/falcon/api.py new file mode 100644 index 0000000..6796dbf --- /dev/null +++ b/falcon/api.py @@ -0,0 +1,323 @@ +"""Python/notebook API entry points for Falcon.""" + +from pathlib import Path +from typing import List, Optional, Union + +from omegaconf import DictConfig, OmegaConf + + +class Config: + """Thin wrapper over OmegaConf DictConfig with fluent override and display helpers.""" + + def __init__(self, cfg: DictConfig): + self._cfg = cfg + + def override(self, *dotted_strings: str) -> "Config": + """Return a new Config with the given dotted overrides applied. + + Example:: + + cfg = falcon.config("config.yml").override( + "buffer.max_samples=500", + "graph.theta.estimator.loop.max_epochs=200", + ) + """ + overrides = OmegaConf.from_dotlist(list(dotted_strings)) + return Config(OmegaConf.merge(self._cfg, overrides)) + + def to_yaml(self) -> str: + """Return the config as a YAML string.""" + return OmegaConf.to_yaml(self._cfg) + + def _repr_markdown_(self) -> str: + return f"```yaml\n{self.to_yaml()}\n```" + + def __repr__(self) -> str: + return f"Config(\n{self.to_yaml()})" + + @property + def _dict_config(self) -> DictConfig: + return self._cfg + + +def config(source) -> Config: + """Load or wrap a Falcon configuration. + + Args: + source: Path to a YAML file (str or Path), a plain dict, or a DictConfig. + + Returns: + :class:`Config` with ``.override()`` and ``.to_yaml()`` methods. + """ + if isinstance(source, (str, Path)): + cfg = OmegaConf.load(source) + elif isinstance(source, dict): + cfg = OmegaConf.create(source) + elif isinstance(source, DictConfig): + cfg = source + else: + raise TypeError( + f"config() expected a path, dict, or DictConfig; got {type(source).__name__}" + ) + return Config(cfg) + + +# --------------------------------------------------------------------------- +# Ray lifecycle +# --------------------------------------------------------------------------- + + +def init(**ray_init_kwargs) -> None: + """Connect to or start a Ray cluster. + + Idempotent: a second call is a no-op if Ray is already initialised. + Pass any ``ray.init()`` keyword argument directly. + + Examples:: + + falcon.init() # local cluster, auto-detect resources + falcon.init(address="auto") # connect to existing local cluster + falcon.init(address="ray://...") # connect to remote cluster + """ + import ray + if ray.is_initialized(): + return + ray_init_kwargs.setdefault("namespace", "falcon") + ray_init_kwargs.setdefault("logging_level", "ERROR") + ray_init_kwargs.setdefault("log_to_driver", True) + ray.init(**ray_init_kwargs) + + +def shutdown() -> None: + """Shut down the Ray cluster started by :func:`falcon.init`.""" + import ray + ray.shutdown() + + +# --------------------------------------------------------------------------- +# Default config for programmatic Graph-based runs +# --------------------------------------------------------------------------- + +_DEFAULT_GRAPH_CONFIG = { + "logging": { + "local": {"enabled": True, "dir": "${paths.graph}"}, + }, + "paths": { + "imports": [], + "graph": "${run_dir}/graph", + "samples": "${run_dir}/samples", + }, + "buffer": { + "min_samples": 4096, + "max_samples": 32768, + "validation_samples": 256, + "simulate_count": 64, + "simulate_when_full": True, + "simulate_interval": 1, + "snapshot_every": 0, + }, + "sample": { + "posterior": {"n": 1000}, + }, +} + + +def _cls_to_target(cls_or_instance) -> str: + """Return a ``_target_`` string or a ```` placeholder.""" + if isinstance(cls_or_instance, str): + return cls_or_instance + obj = cls_or_instance + if isinstance(obj, type): + cls = obj + else: + cls = type(obj) + mod = getattr(cls, "__module__", None) + qualname = getattr(cls, "__qualname__", cls.__name__) + if mod in (None, "__main__") or not isinstance(cls_or_instance, type): + return f"" + return f"{mod}.{qualname}" + + +def _graph_to_config_dict(graph) -> dict: + """Serialise a Graph to a config dict with live-object placeholders.""" + import numpy as np + result = {} + for node in graph.node_list: + nd: dict = {} + if node.parents: + nd["parents"] = list(node.parents) + if node.evidence: + nd["evidence"] = list(node.evidence) + if node.scaffolds: + nd["scaffolds"] = list(node.scaffolds) + + # Simulator + sim = node.simulator_cls + target = _cls_to_target(sim) + if target.startswith("" + ) + else: + nd["observed"] = True + + # Ray actor config + if node.actor_config: + nd["ray"] = dict(node.actor_config) + + result[node.name] = nd + return result + + +# --------------------------------------------------------------------------- +# Config preparation (shared between launch() and the CLI) +# --------------------------------------------------------------------------- + + +def _prepare_config( + target, + output: Optional[Union[str, Path]], + overrides: Optional[List[str]], +): + """Resolve *target* into a runnable OmegaConf config with ``run_dir`` set. + + Returns: + (cfg, output_dir_path, prebuilt_graph_or_None) + """ + from datetime import datetime + from falcon.core.graph import Graph + from falcon.core.run_name import generate_run_dir + + OmegaConf.register_new_resolver("now", lambda fmt: datetime.now().strftime(fmt), replace=True) + + # Determine output directory first + if output is None: + run_dir = generate_run_dir() + else: + run_dir = str(output) + + run_dir_path = Path(run_dir) + saved_config = run_dir_path / "config.yml" + + prebuilt_graph = None + + if isinstance(target, Graph): + # Graph target: always use the live graph object (saved config has + # placeholders that can't be re-parsed). + prebuilt_graph = target + if saved_config.exists(): + cfg = OmegaConf.load(saved_config) + else: + base = OmegaConf.create(_DEFAULT_GRAPH_CONFIG) + graph_dict = _graph_to_config_dict(target) + cfg = OmegaConf.merge(base, {"graph": OmegaConf.create(graph_dict)}) + elif saved_config.exists(): + # Resume an existing non-Graph run from its saved config. + cfg = OmegaConf.load(saved_config) + elif isinstance(target, Config): + cfg = OmegaConf.merge(target._dict_config, {}) + elif isinstance(target, DictConfig): + cfg = OmegaConf.merge(target, {}) + elif isinstance(target, dict): + cfg = OmegaConf.create(target) + elif isinstance(target, (str, Path)): + cfg = OmegaConf.load(target) + else: + raise TypeError( + f"launch() target must be a Graph, Config, path, or dict; " + f"got {type(target).__name__}" + ) + + # Apply overrides + if overrides: + cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(list(overrides))) + + # Inject run_dir and resolve interpolations + cfg.run_dir = run_dir + OmegaConf.resolve(cfg) + + # Persist config so the run is reproducible + run_dir_path.mkdir(parents=True, exist_ok=True) + if not saved_config.exists(): + OmegaConf.save(cfg, saved_config) + + return cfg, run_dir_path, prebuilt_graph + + +# --------------------------------------------------------------------------- +# launch() +# --------------------------------------------------------------------------- + + +def launch( + target, + output=None, + *, + overrides=None, + auto_sample: bool = True, + timeout: float = None, + wait: bool = True, +): + """Run a Falcon training pipeline from a notebook or script. + + Blocks by default (``wait=True``) and returns a finished :class:`Run`. + Lazily calls :func:`falcon.init` if Ray is not yet initialised. + + Args: + target: A :class:`Config` object, path to a YAML config file, or plain + dict. Passing a ``Graph`` is supported in Step 5. + output: Output directory. Auto-generated (``output/``) + if *None*. An existing directory with a ``config.yml`` is resumed. + overrides: Iterable of dotted override strings applied on top of + *target* (e.g. ``["buffer.max_samples=500"]``). + auto_sample: Generate posterior samples after training (default True). + timeout: Stop training gracefully after this many seconds. + wait: Block until training completes (default True). ``wait=False`` is + not yet implemented. + + Returns: + :class:`falcon.core.run_loader.Run` with config, metrics, and samples. + """ + if not wait: + raise NotImplementedError( + "wait=False (non-blocking launch) is not yet implemented. " + "Use wait=True (the default) or run falcon.launch() in a thread manually." + ) + + from falcon.cli import _run_pipeline + from falcon.core.run_loader import load_run + + cfg, output_dir, prebuilt_graph = _prepare_config(target, output, overrides) + + # Lazy Ray init + import ray + if not ray.is_initialized(): + init() + + obs = prebuilt_graph._api_observations if prebuilt_graph is not None else None + _run_pipeline( + cfg, + auto_sample=auto_sample, + timeout=timeout, + graph=prebuilt_graph, + observations=obs, + ) + + return load_run(output_dir) diff --git a/falcon/cli.py b/falcon/cli.py index 44a2316..46aa71a 100644 --- a/falcon/cli.py +++ b/falcon/cli.py @@ -483,10 +483,43 @@ def _save_samples(samples, sample_cfg, sample_type, graph, cfg, info_fn=print): info_fn(f"Saved {num_samples} {sample_type} samples to: {output_dir}/") -def launch_mode(cfg, interactive: bool = False, log_lines: int = 16, auto_sample: bool = True, timeout: float = None) -> None: - """Launch mode: Full training and inference pipeline.""" - import logging - import threading +def _run_pipeline( + cfg, + *, + auto_sample: bool = True, + timeout: float = None, + stop_check=None, + log_handler=None, + on_graph_ready=None, + summary_sink=None, + graph=None, + observations=None, +) -> Path: + """Pure training pipeline, decoupled from CLI frontend and Ray lifecycle. + + Assumes Ray is already initialised. Does not call ray.init() or ray.shutdown(). + + Args: + cfg: Resolved OmegaConf config. + auto_sample: Run configured post-training sampling when True. + timeout: Stop training after this many seconds (None = no limit). + stop_check: Callable returning True when training should stop gracefully. + Defaults to never stopping. + log_handler: Optional logging.Handler to replace the stdout console + handler (used by the TUI to route log lines to its display buffer). + on_graph_ready: Optional callable(graph_path: Path) invoked just before + training starts; used by the TUI to start its status polling thread. + summary_sink: Optional list; the end-of-run summary lines are appended + to it so the caller can echo them after display teardown. + graph: Pre-built Graph object (from the Python API). When provided, + ``cfg.graph`` is not parsed; this graph is used directly. + observations: Dict of node_name -> np.ndarray from graph._api_observations. + Only used when *graph* is provided. + + Returns: + output_dir Path. + """ + import logging as _logging import time as _time import torch import ray @@ -495,187 +528,79 @@ def launch_mode(cfg, interactive: bool = False, log_lines: int = 16, auto_sample from falcon.core.graph import create_graph_from_config from falcon.core.logger import Logger, set_logger, info - launch_start = _time.time() + if stop_check is None: + stop_check = lambda: False - # Initialize interactive display or graceful shutdown handler - display = None - shutdown_handler = None - if interactive: - try: - from falcon.interactive import InteractiveDisplay - # Footer height = log_lines + 4 (separator, status bar, sub-separator, help) - display = InteractiveDisplay(footer_height=log_lines + 4) - display.start() - except ImportError: - print("Interactive display unavailable (install falcon-sbi[blessed] to enable it)") - if display is None: - # Non-interactive mode: install double Ctrl+C handler - shutdown_handler = _GracefulShutdown() - shutdown_handler.install() - - # Get output directory from config + launch_start = _time.time() output_dir = Path(cfg.run_dir) path_cfg = _resolve_paths(cfg) - # Generate wandb group if not set - use run-dir folder name + # Build logging config logging_cfg = OmegaConf.to_container(cfg.get("logging", {}), resolve=True) if logging_cfg.get("wandb", {}).get("enabled", False): if not logging_cfg.get("wandb", {}).get("group"): - # Use the run-dir folder name as the group name logging_cfg.setdefault("wandb", {})["group"] = output_dir.name - - # Ensure local dir is set to graph path logging_cfg.setdefault("local", {})["dir"] = path_cfg["graph"] # Create driver logger and set as module-level logger - # This enables falcon.info(), falcon.log() etc. for DeployedGraph and other components driver_logger = Logger("driver", logging_cfg, capture_exceptions=True) set_logger(driver_logger) - # If interactive mode, replace console handler with one that routes to display - if display: + # Replace stdout handler with the injected one (e.g. TUI routing) + if log_handler is not None: for handler in driver_logger._logger.handlers[:]: - if isinstance(handler, logging.StreamHandler) and handler.stream == sys.stdout: + if isinstance(handler, _logging.StreamHandler) and handler.stream == sys.stdout: driver_logger._logger.removeHandler(handler) - # Add custom handler that routes to display - interactive_handler = logging.StreamHandler(_InteractiveStream(display)) - interactive_handler.setFormatter(logging.Formatter( - '%(asctime)s [%(levelname)s] %(message)s', - datefmt='%H:%M:%S' - )) - interactive_handler.setLevel(logging.INFO) - driver_logger._logger.addHandler(interactive_handler) break + driver_logger._logger.addHandler(log_handler) - # Log startup info + # Startup info info(f"falcon v{falcon.__version__}") info(f"Output: {output_dir}") - # Initialize Ray - ray_init_args = cfg.get("ray", {}).get("init", {}) - # Forward actor stdout/stderr to driver when console.level is set, - # so node log messages and crash output reach the terminal. - console_level = logging_cfg.get("console", {}).get("level", None) - ray_init_args.setdefault("log_to_driver", console_level is not None) - # Use a fixed namespace so falcon monitor can discover actors - ray_init_args.setdefault("namespace", "falcon") - # Suppress Ray startup banner - ray_init_args.setdefault("logging_level", "ERROR") - ray.init(**ray_init_args) - - # Show Ray cluster info with resources + # Ray cluster info (Ray must already be initialised by the caller) ctx = ray.get_runtime_context() gcs_address = ctx.gcs_address - is_local = ray_init_args.get("address") is None + is_local = cfg.get("ray", {}).get("init", {}).get("address") is None ray_status = "new local instance" if is_local else "existing cluster" resources = ray.cluster_resources() cpu = int(resources.get("CPU", 0)) gpu = int(resources.get("GPU", 0)) mem_gb = resources.get("memory", 0) / (1024**3) - info(f"Ray: {gcs_address} ({ray_status})") info(f"Resources: {cpu} CPU, {gpu} GPU, {mem_gb:.1f} GB") - ######################## - ### Model definition ### - ######################## - - # Instantiate model components directly from graph - graph, observations = create_graph_from_config(cfg.graph, _cfg=cfg) - - # Convert observations to tensors, adding batch dimension - observations = { - k: torch.from_numpy(v).unsqueeze(0) for k, v in observations.items() - } - - # Log graph info + # Build graph and observations + if graph is None: + graph, obs_raw = create_graph_from_config(cfg.graph, _cfg=cfg) + observations_tensors = { + k: torch.from_numpy(v).unsqueeze(0) for k, v in obs_raw.items() + } + else: + import numpy as np + observations_tensors = { + k: torch.from_numpy(np.asarray(v)).unsqueeze(0) + for k, v in (observations or {}).items() + } info(str(graph)) - for name, shape in observations.items(): + for name, shape in observations_tensors.items(): info(f"Observed: {name} {list(shape.shape)}") - #################### - ### Run analysis ### - #################### - - # Start status polling thread for interactive mode - status_thread = None graph_path = Path(path_cfg["graph"]) - if display: - # Set log directory so display can read node output.log files - display.set_log_dir(str(graph_path)) - - def poll_status(): - """Background thread to poll MonitorBridge and update display.""" - import time - while display.is_running: - try: - bridge = ray.get_actor("falcon:monitor_bridge") - status = ray.get(bridge.get_status.remote()) - - # Update nodes - for name, node_status in status.get("nodes", {}).items(): - display.update_node( - name=name, - status=node_status.get("status", "unknown"), - current_epoch=node_status.get("current_epoch", 0), - total_epochs=node_status.get("total_epochs", 0), - loss=node_status.get("loss"), - samples=node_status.get("samples", 0), - ) - - # Add dataset manager as a viewable node (for its output.log) - display.update_node(name="dataset", status="active") - - # Update buffer stats - buffer = status.get("buffer", {}) - display.update_buffer( - training=buffer.get("training", 0), - validation=buffer.get("validation", 0), - ) - except Exception: - pass # MonitorBridge may not be ready yet - # Redraw footer to refresh log tail - with display._lock: - display._draw_footer() - - time.sleep(1.0) - - status_thread = threading.Thread(target=poll_status, daemon=True) - status_thread.start() - - # Create stop check callback for graceful shutdown (handles Ctrl+C and timeout) - _start_time = _time.time() - _timeout_logged = False - - def stop_check(): - nonlocal _timeout_logged - # Check user interrupt (Ctrl+C) - if display and display.stop_requested: - return True - if shutdown_handler and shutdown_handler.stop_requested: - return True - # Check timeout - if timeout is not None: - elapsed = _time.time() - _start_time - if elapsed >= timeout: - if not _timeout_logged: - info(f"Timeout reached ({timeout}s), stopping gracefully...") - _timeout_logged = True - return True - return False + # Notify caller that graph path is known (TUI uses this to start status thread) + if on_graph_ready is not None: + on_graph_ready(graph_path) run_status = "completed" deployed_graph = None try: - # 1) Deploy graph (pass logging config) deployed_graph = falcon.DeployedGraph( graph, import_dirs=path_cfg["imports"], log_config=logging_cfg, ) - # 2) Prepare dataset manager for deployed graph and store initial samples from omegaconf import OmegaConf as _OmegaConf from falcon.core.raystore import BufferConfig as _BufferConfig buffer_cfg = _OmegaConf.merge(_OmegaConf.structured(_BufferConfig), cfg.buffer) @@ -686,50 +611,27 @@ def stop_check(): log_config=logging_cfg, ) - deployed_graph.launch(dataset_manager, observations, graph_path=graph_path, stop_check=stop_check) - - ############################# - ### Posterior sampling ### - ############################# + deployed_graph.launch(dataset_manager, observations_tensors, graph_path=graph_path, stop_check=stop_check) - # Check if posterior sampling is configured and enabled + # Auto-sample posterior sample_cfg = cfg.get("sample", {}).get("posterior", {}) num_posterior_samples = sample_cfg.get("n", 0) - if auto_sample and num_posterior_samples > 0: info(f"Generating {num_posterior_samples} posterior samples...") - - sample_refs = deployed_graph.sample_posterior(num_posterior_samples, observations) + sample_refs = deployed_graph.sample_posterior(num_posterior_samples, observations_tensors) samples = deployed_graph._refs_to_arrays(sample_refs) + _save_samples(samples=samples, sample_cfg=sample_cfg, sample_type="posterior", + graph=graph, cfg=cfg, info_fn=info) - # Save posterior samples - _save_samples( - samples=samples, - sample_cfg=sample_cfg, - sample_type="posterior", - graph=graph, - cfg=cfg, - info_fn=info, - ) - - # Check if PPD sampling is configured and enabled + # Auto-sample PPD ppd_cfg = cfg.get("sample", {}).get("ppd", {}) num_ppd_samples = ppd_cfg.get("n", 0) - if auto_sample and num_ppd_samples > 0: info(f"Generating {num_ppd_samples} PPD samples...") - - sample_refs = deployed_graph.sample_ppd(num_ppd_samples, observations) + sample_refs = deployed_graph.sample_ppd(num_ppd_samples, observations_tensors) samples = deployed_graph._refs_to_arrays(sample_refs) - - _save_samples( - samples=samples, - sample_cfg=ppd_cfg, - sample_type="ppd", - graph=graph, - cfg=cfg, - info_fn=info, - ) + _save_samples(samples=samples, sample_cfg=ppd_cfg, sample_type="ppd", + graph=graph, cfg=cfg, info_fn=info) except KeyboardInterrupt: run_status = "interrupted" @@ -738,43 +640,142 @@ def stop_check(): run_status = f"failed ({type(e).__name__}: {e})" raise finally: - ########################## - ### Clean up resources ### - ########################## - - # A graceful Ctrl+C / timeout exits the launch normally, not via an - # exception, so reflect that here. - if run_status == "completed": - if (display and display.stop_requested) or ( - shutdown_handler and shutdown_handler.stop_requested - ): - run_status = "interrupted" - - # Build the end-of-run summary and route it through the driver logger - # so it lands in driver/output.log (and, in plain mode, on stdout). + # A graceful stop_check exit is not an exception, so detect it here. + if run_status == "completed" and stop_check(): + run_status = "interrupted" + summary_lines = _build_run_summary( run_status, output_dir, cfg, deployed_graph, start_time=launch_start, end_time=_time.time(), ) for line in summary_lines: info(line) + if summary_sink is not None: + summary_sink.extend(summary_lines) + + if deployed_graph is not None: + deployed_graph.shutdown() + driver_logger.shutdown() + + return output_dir + + +def launch_mode(cfg, interactive: bool = False, log_lines: int = 16, auto_sample: bool = True, timeout: float = None) -> None: + """Launch mode: CLI frontend over _run_pipeline.""" + import logging + import threading + import time as _time + import ray + from omegaconf import OmegaConf + + # Set up interactive display or graceful shutdown handler + display = None + shutdown_handler = None + if interactive: + try: + from falcon.interactive import InteractiveDisplay + display = InteractiveDisplay(footer_height=log_lines + 4) + display.start() + except ImportError: + print("Interactive display unavailable (install falcon-sbi[blessed] to enable it)") + if display is None: + shutdown_handler = _GracefulShutdown() + shutdown_handler.install() + + # Initialize Ray + logging_cfg = OmegaConf.to_container(cfg.get("logging", {}), resolve=True) + ray_init_args = cfg.get("ray", {}).get("init", {}) + console_level = logging_cfg.get("console", {}).get("level", None) + ray_init_args.setdefault("log_to_driver", console_level is not None) + ray_init_args.setdefault("namespace", "falcon") + ray_init_args.setdefault("logging_level", "ERROR") + ray.init(**ray_init_args) + + # Build stop_check: handles Ctrl+C, display stop, and timeout + _start_time = _time.time() + _timeout_logged = False + + def stop_check(): + nonlocal _timeout_logged + if display and display.stop_requested: + return True + if shutdown_handler and shutdown_handler.stop_requested: + return True + if timeout is not None: + elapsed = _time.time() - _start_time + if elapsed >= timeout: + if not _timeout_logged: + from falcon.core.logger import info + info(f"Timeout reached ({timeout}s), stopping gracefully...") + _timeout_logged = True + return True + return False + + # Build log handler for TUI routing (replaces stdout in the driver logger) + log_handler = None + if display: + interactive_handler = logging.StreamHandler(_InteractiveStream(display)) + interactive_handler.setFormatter(logging.Formatter( + '%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S' + )) + interactive_handler.setLevel(logging.INFO) + log_handler = interactive_handler + + # Build on_graph_ready: TUI sets log dir and starts status polling thread + def on_graph_ready(graph_path): + if display is None: + return + display.set_log_dir(str(graph_path)) + + def poll_status(): + import time + while display.is_running: + try: + bridge = ray.get_actor("falcon:monitor_bridge") + status = ray.get(bridge.get_status.remote()) + for name, node_status in status.get("nodes", {}).items(): + display.update_node( + name=name, + status=node_status.get("status", "unknown"), + current_epoch=node_status.get("current_epoch", 0), + total_epochs=node_status.get("total_epochs", 0), + loss=node_status.get("loss"), + samples=node_status.get("samples", 0), + ) + display.update_node(name="dataset", status="active") + buffer = status.get("buffer", {}) + display.update_buffer( + training=buffer.get("training", 0), + validation=buffer.get("validation", 0), + ) + except Exception: + pass + with display._lock: + display._draw_footer() + time.sleep(1.0) - # Stop interactive display first (restores terminal) + threading.Thread(target=poll_status, daemon=True).start() + + summary_lines = [] + try: + _run_pipeline( + cfg, + auto_sample=auto_sample, + timeout=timeout, + stop_check=stop_check, + log_handler=log_handler, + on_graph_ready=on_graph_ready, + summary_sink=summary_lines, + ) + finally: if display: display.stop() - # The TUI used the alternate screen buffer, so nothing the user - # saw survives. Echo the summary to real stdout as the receipt. + # TUI used alternate screen buffer; echo summary to restored terminal. for line in summary_lines: print(line) - - # Uninstall shutdown handler if shutdown_handler: shutdown_handler.uninstall() - if deployed_graph is not None: - deployed_graph.shutdown() - driver_logger.shutdown() - def sample_mode(cfg, sample_type: str) -> None: """Sample mode: Generate samples using different sampling strategies. diff --git a/falcon/core/base_estimator.py b/falcon/core/base_estimator.py index 6963a38..af30d00 100644 --- a/falcon/core/base_estimator.py +++ b/falcon/core/base_estimator.py @@ -14,92 +14,62 @@ class BaseEstimator(ABC): """ Fully abstract base class defining the estimator interface. - All methods are abstract - no implementation details. - Concrete implementations must provide all functionality. + Subclasses implement a two-phase lifecycle: - Conditions are passed as Dict[str, Tensor] mapping node names to values. - Sampling methods return dicts with 'value' (ndarray) and optionally 'log_prob' (ndarray). + 1. ``__init__``: pure config storage — stores all parameters as instance + attributes. Defaults live here; nothing runtime is wired up. + + 2. ``setup()``: load-bearing runtime wiring — called by ``NodeWrapper`` + inside a Ray actor before training begins. Applies any flat YAML + overrides (``config`` dict), then initialises networks, devices, and + all runtime objects. + + Example:: + + graph.add_node("z", estimator=Flow(max_epochs=300, net_type="nsf")) """ @abstractmethod - async def train(self, buffer) -> None: - """ - Train the estimator. + def setup( + self, + simulator_instance, + theta_key: Optional[str], + condition_keys, + ) -> None: + """Wire up runtime components. + + Called by ``NodeWrapper`` before training. Args: - buffer: BufferView providing access to training/validation data + simulator_instance: Live prior/simulator, already constructed. + theta_key: Name of the parameter node being estimated. + condition_keys: List of evidence/scaffold node names. """ - pass @abstractmethod - def sample_prior( - self, num_samples: int, conditions: Optional[Conditions] = None - ) -> dict: - """ - Sample from the prior distribution. - - Args: - num_samples: Number of samples to generate - conditions: Conditioning values from parent nodes (usually None for prior) - - Returns: - Dict with 'value' (ndarray) and optionally 'log_prob' (ndarray) - """ + async def train(self, buffer) -> None: pass @abstractmethod - def sample_posterior( - self, num_samples: int, conditions: Optional[Conditions] = None - ) -> dict: - """ - Sample from the posterior distribution. - - Args: - num_samples: Number of samples to generate - conditions: Dict mapping node names to condition tensors - - Returns: - Dict with 'value' (ndarray) and optionally 'log_prob' (ndarray) - """ + def sample_prior(self, num_samples: int, conditions: Optional[Conditions] = None) -> dict: pass @abstractmethod - def sample_proposal( - self, num_samples: int, conditions: Optional[Conditions] = None - ) -> dict: - """ - Sample from the proposal distribution for adaptive resampling. - - Args: - num_samples: Number of samples to generate - conditions: Dict mapping node names to condition tensors + def sample_posterior(self, num_samples: int, conditions: Optional[Conditions] = None) -> dict: + pass - Returns: - Dict with 'value' (ndarray) and optionally 'log_prob' (ndarray) - """ + @abstractmethod + def sample_proposal(self, num_samples: int, conditions: Optional[Conditions] = None) -> dict: pass @abstractmethod def save(self, node_dir: Path) -> None: - """ - Save estimator state to directory. - - Args: - node_dir: Directory to save state to - """ pass @abstractmethod def load(self, node_dir: Path) -> None: - """ - Load estimator state from directory. - - Args: - node_dir: Directory to load state from - """ pass @abstractmethod def interrupt(self) -> None: - """Terminate training loop.""" pass diff --git a/falcon/core/deployed_graph.py b/falcon/core/deployed_graph.py index bd273cf..7185186 100644 --- a/falcon/core/deployed_graph.py +++ b/falcon/core/deployed_graph.py @@ -1,6 +1,5 @@ import time import ray -import asyncio import torch import os import sys @@ -154,20 +153,36 @@ def __init__(self, node, graph, import_dirs=None, log_config=None): # Status tracking for monitoring self._status = "initializing" - simulator_cls = LazyLoader(node.simulator_cls) - self.simulator_instance = simulator_cls(**node.simulator_config) + # Live instances (from Python API) are used directly; string / class + # paths go through LazyLoader for deferred import + instantiation. + if isinstance(node.simulator_cls, (str, type)): + simulator_cls = LazyLoader(node.simulator_cls) + self.simulator_instance = simulator_cls(**node.simulator_config) + else: + self.simulator_instance = node.simulator_cls # Condition keys for embedding (evidence + scaffolds) self.condition_keys = self.node.evidence + self.node.scaffolds debug(f"Condition keys: {self.condition_keys}") if node.estimator_cls is not None: - estimator_cls = LazyLoader(node.estimator_cls) - self.estimator_instance = estimator_cls( + from falcon.core.base_estimator import BaseEstimator as _BaseEstimator + if isinstance(node.estimator_cls, _BaseEstimator): + # Notebook path: already a configured instance (e.g. Flow(max_epochs=200)) + self.estimator_instance = node.estimator_cls + elif isinstance(node.estimator_cls, (str, type)): + # YAML path: pass flat config dict as kwargs to __init__ + estimator_cls = LazyLoader(node.estimator_cls) + self.estimator_instance = estimator_cls(**node.estimator_config) + else: + raise TypeError( + f"estimator_cls must be a BaseEstimator instance, class, or " + f"string; got {type(node.estimator_cls).__name__}" + ) + self.estimator_instance.setup( self.simulator_instance, theta_key=node.name, condition_keys=self.condition_keys, - config=node.estimator_config, ) else: self.estimator_instance = None @@ -427,8 +442,8 @@ def get_status(self) -> dict: if status["loss_history"]: status["loss"] = status["loss_history"][-1] status["current_epoch"] = len(est.history.get("epochs", [])) - if hasattr(est, "loop_config"): - status["total_epochs"] = est.loop_config.max_epochs + if hasattr(est, "max_epochs"): + status["total_epochs"] = est.max_epochs if hasattr(est, "history") and est.history.get("n_samples"): status["samples"] = est.history["n_samples"][-1] @@ -777,9 +792,9 @@ def launch(self, dataset_manager, observations, graph_path=None, stop_check=None graph_path: Path to save/load graph stop_check: Optional callable that returns True when graceful stop is requested """ - asyncio.run(self._launch(dataset_manager, observations, graph_path=graph_path, stop_check=stop_check)) + self._launch(dataset_manager, observations, graph_path=graph_path, stop_check=stop_check) - async def _launch(self, dataset_manager, observations, graph_path=None, stop_check=None): + def _launch(self, dataset_manager, observations, graph_path=None, stop_check=None): # Load graph if saved model files exist (not just logging directories) if graph_path is not None and any(graph_path.glob("*/*.pth")): self.load(graph_path) diff --git a/falcon/core/graph.py b/falcon/core/graph.py index ea483fa..9fb64af 100644 --- a/falcon/core/graph.py +++ b/falcon/core/graph.py @@ -54,8 +54,27 @@ def __init__( self.sample_chunk_size = sample_chunk_size +def _short_cls_name(obj) -> str: + """Return a short display name for a class, instance, or string target.""" + if obj is None: + return "None" + if isinstance(obj, str): + if obj.startswith("").strip() + return obj.rsplit(".", 1)[-1] + if isinstance(obj, type): + return obj.__name__ + return type(obj).__name__ + + class Graph: - def __init__(self, node_list): + def __init__(self, node_list=None): + # Observations supplied directly as arrays via add_node(observed=array) + self._api_observations = {} + self._build(list(node_list) if node_list is not None else []) + + def _build(self, node_list): + """(Re)compute all derived topology from *node_list*.""" # Storing the node list self.node_list = node_list self.node_dict = {node.name: node for node in node_list} @@ -89,7 +108,8 @@ def __init__(self, node_list): if name in backward_set: continue backward_set.add(name) - for parent in self.forward_deps[name]: + # Guard: node may reference a not-yet-added peer + for parent in self.forward_deps.get(name, []): if parent not in backward_set: queue.append(parent) @@ -112,6 +132,79 @@ def __init__(self, node_list): backward_names, self.backward_deps ) + def add_node( + self, + name: str, + simulator, + estimator=None, + *, + parents=None, + evidence=None, + scaffolds=None, + observed=None, + num_actors: int = 1, + sample_chunk_size: int = 0, + **ray_kwargs, + ) -> "Graph": + """Add a node to the graph and return *self* for chaining. + + Args: + name: Node name (must be unique in this graph). + simulator: Simulator class, string ``_target_``, or a live instance. + Live instances (e.g. ``Product([...])``) are shipped to Ray + actors via cloudpickle. + estimator: Estimator class, string ``_target_``, config builder + (e.g. ``Flow(max_epochs=300)``), or ``None`` for + observation-only nodes. + parents: List of parent node names (forward / simulation direction). + evidence: List of evidence node names (inference direction). + scaffolds: List of scaffold node names. + observed: Observed value — a numpy/torch array (used directly), or + ``True`` / a file-path string (YAML semantics). + num_actors: Number of Ray actors to spawn for this node. + sample_chunk_size: Chunk size for sampling (0 = no chunking). + **ray_kwargs: Node-level Ray actor options, prefixed with ``ray_`` + (e.g. ``ray_num_gpus=0.5``, ``ray_num_cpus=2``, + ``ray_runtime_env={...}``). The ``ray_`` prefix is stripped + before passing to Ray. + + Returns: + *self*, so calls can be chained. + """ + import numpy as np + + # Collect actor config from ray_* kwargs + actor_config = {} + for key, val in ray_kwargs.items(): + if not key.startswith("ray_"): + raise TypeError( + f"add_node() got unexpected keyword argument '{key}'. " + f"Ray actor options must be prefixed with 'ray_' " + f"(e.g. ray_num_gpus=0.5)." + ) + actor_config[key[4:]] = val # strip "ray_" prefix + + # Handle observed= as a live array + if observed is not None and not isinstance(observed, (str, bool)): + self._api_observations[name] = np.asarray(observed) + observed = True # mark as observed for the Node + + node = Node( + name=name, + simulator_cls=simulator, + estimator_cls=estimator, + parents=list(parents or []), + evidence=list(evidence or []), + scaffolds=list(scaffolds or []), + observed=bool(observed) if observed is not None else False, + actor_config=actor_config, + num_actors=num_actors, + sample_chunk_size=sample_chunk_size, + ) + + self._build(self.node_list + [node]) + return self + def get_parents(self, node_name): return self.forward_deps[node_name] @@ -163,6 +256,61 @@ def __add__(self, other): new_node_list = self.node_list + other.node_list return Graph(new_node_list) + # ------------------------------------------------------------------ + # Rich display helpers + # ------------------------------------------------------------------ + + def _repr_html_(self) -> str: + """Mermaid flowchart for Jupyter/Colab.""" + import uuid + + uid = uuid.uuid4().hex[:8] + lines = ["flowchart LR"] + + for node in self.node_list: + sim = _short_cls_name(node.simulator_cls) + if node.observed: + label = f'"{node.name}
{sim} · observed"' + color = "fill:#d5f5e3,stroke:#27ae60" + elif node.estimator_cls is not None: + est = _short_cls_name(node.estimator_cls) + label = f'"{node.name}
sim:{sim}
est:{est}
"' + color = "fill:#d6eaf8,stroke:#2980b9" + else: + label = f'"{node.name}
{sim}"' + color = "fill:#fef9e7,stroke:#f39c12" + lines.append(f" {node.name}[{label}]") + lines.append(f" style {node.name} {color}") + + for node in self.node_list: + for parent in node.parents: + lines.append(f" {parent} --> {node.name}") + + for node in self.node_list: + for ev in node.evidence: + lines.append(f" {ev} -.->|evidence| {node.name}") + + mermaid_src = "\n".join(lines) + return f"""
+
+{mermaid_src}
+
+
+""" + def __str__(self): # Return graph structure # - Based on topological sort diff --git a/falcon/core/raystore.py b/falcon/core/raystore.py index b9434bf..db82f32 100644 --- a/falcon/core/raystore.py +++ b/falcon/core/raystore.py @@ -135,7 +135,7 @@ class SampleStatus(IntEnum): DELETED = 4 # Permanently deleted -@ray.remote(name="DatasetManager") +@ray.remote class DatasetManagerActor: def __init__( self, diff --git a/falcon/core/run_loader.py b/falcon/core/run_loader.py index 5f90d6f..a45a228 100644 --- a/falcon/core/run_loader.py +++ b/falcon/core/run_loader.py @@ -122,6 +122,116 @@ def observations(self) -> Dict[str, np.ndarray]: return self._observations + def plot_metrics(self, nodes=None, metrics=("train", "val"), figsize=None): + """Plot training metric curves. + + Args: + nodes: Node names to plot. Defaults to all nodes with metric data. + metrics: Metric names to look for (e.g. ``("train", "val")``). + figsize: Matplotlib figure size. Auto-scaled if *None*. + + Returns: + ``matplotlib.figure.Figure`` + """ + import matplotlib.pyplot as plt + + available = self.metrics.list_nodes() + plot_nodes = [n for n in (nodes or available) if n in available] + + if not plot_nodes: + print("No metric data found.") + return None + + n_cols = len(plot_nodes) + fig, axes = plt.subplots(1, n_cols, figsize=figsize or (5 * n_cols, 4), squeeze=False) + axes = axes[0] + + for ax, node_name in zip(axes, plot_nodes): + node_reader = self.metrics[node_name] + node_metrics = node_reader.list_metrics() + plotted = False + for metric in metrics: + if metric in node_metrics: + try: + r = node_reader[metric] + ax.plot(r.steps, r.values, label=metric) + plotted = True + except Exception: + pass + if not plotted: + ax.text(0.5, 0.5, "no data", ha="center", va="center", transform=ax.transAxes) + ax.set_title(node_name) + ax.set_xlabel("step") + ax.set_ylabel("loss") + if plotted: + ax.legend() + + fig.tight_layout() + return fig + + def _repr_html_(self) -> str: + """Status card for Jupyter/Colab.""" + rows = [] + + # Per-node metrics summary + metric_rows = [] + for node_name in self.metrics.list_nodes(): + node_reader = self.metrics[node_name] + available = node_reader.list_metrics() + final_loss = "" + for key in ("val", "train"): + if key in available: + try: + vals = node_reader[key].values + if len(vals): + final_loss = f"{vals[-1]:.4f}" + except Exception: + pass + break + n_steps = "" + if "epoch" in available: + try: + n_steps = str(int(node_reader["epoch"].values[-1])) + except Exception: + pass + td = "padding:2px 8px" + metric_rows.append( + f"" + f"{node_name}" + f"{final_loss}" + f"{n_steps}" + f"" + ) + + if metric_rows: + rows.append( + "" + "" + "" + "" + "" + "" + + "".join(metric_rows) + + "
nodefinal lossepochs
" + ) + + # Sample counts + try: + post = self.samples.posterior + n_files = len(list(post._sample_dir.glob("*.npz"))) if hasattr(post, "_sample_dir") else "?" + rows.append(f"
posterior samples: {n_files} file(s)
") + except Exception: + pass + + inner = "\n".join(rows) + return ( + f"
" + f"Run {self.run_dir}" + f"{inner}" + f"
" + ) + def __repr__(self): return f"" diff --git a/falcon/embeddings/__init__.py b/falcon/embeddings/__init__.py index 2fb490f..77a83ca 100644 --- a/falcon/embeddings/__init__.py +++ b/falcon/embeddings/__init__.py @@ -5,8 +5,8 @@ """ from falcon.embeddings.builder import instantiate_embedding, EmbeddingWrapper -from falcon.embeddings.norms import RunningNorm, LazyOnlineNorm, DiagonalWhitener, hartley_transform -from falcon.embeddings.svd import PCAProjector +from falcon.embeddings.norms import RunningNorm, LazyOnlineNorm, DiagonalWhitener, ToeplitzWhitener, hartley_transform +from falcon.embeddings.svd import PCAProjector, DynamicSVD __all__ = [ "instantiate_embedding", @@ -14,6 +14,8 @@ "RunningNorm", "LazyOnlineNorm", "DiagonalWhitener", + "ToeplitzWhitener", "hartley_transform", "PCAProjector", + "DynamicSVD", ] diff --git a/falcon/embeddings/builder.py b/falcon/embeddings/builder.py index 48d4c57..4f71a9d 100644 --- a/falcon/embeddings/builder.py +++ b/falcon/embeddings/builder.py @@ -333,8 +333,28 @@ def _flatten_config_to_modules( return modules, input_keys_list, output_keys, temp_counter +class _PassthroughEmbedding(nn.Module): + """Identity embedding: concatenates all condition tensors along the last dim. + + Casts to float32 so the output is compatible with the default network dtype. + """ + + def forward(self, data_dict: Dict[str, Any]): + import torch + tensors = [v.float() for v in data_dict.values() if hasattr(v, "shape")] + if not tensors: + raise ValueError("PassthroughEmbedding received an empty conditions dict.") + return torch.cat(tensors, dim=-1) + + def instantiate_embedding(embedding_config: Dict[str, Any]) -> EmbeddingWrapper: - """Instantiate embedding pipeline from config.""" + """Instantiate embedding pipeline from config. + + When *embedding_config* is ``None``, returns a pass-through embedding that + concatenates all condition tensors along the last dimension. + """ + if embedding_config is None: + return _PassthroughEmbedding() required_input_keys = _collect_input_keys(embedding_config) modules, input_keys_list, output_keys, _ = _flatten_config_to_modules( embedding_config diff --git a/falcon/embeddings/norms.py b/falcon/embeddings/norms.py index 1bd55d5..ddaa547 100644 --- a/falcon/embeddings/norms.py +++ b/falcon/embeddings/norms.py @@ -120,6 +120,40 @@ def hartley_transform(x): return fft.real - fft.imag +class ToeplitzWhitener(torch.nn.Module): + """Whitener for 1D time series assuming stationary (Toeplitz) noise covariance. + + Estimates per-frequency variance via EMA in Hartley space and whitens by + dividing by the estimated std. Noise is assumed zero-mean. + + update(noise) — update EMA variance from a batch of noise samples + __call__(x) — whiten x (Hartley → divide by std → inverse Hartley) + """ + + def __init__(self, momentum: float = 0.1, eps: float = 1e-8) -> None: + super().__init__() + self.momentum = momentum + self.eps = eps + self.register_buffer("running_var", None) + self.initialized = False + + def update(self, noise: torch.Tensor) -> None: + """Update EMA variance from noise samples of shape (batch_size, T).""" + h = hartley_transform(noise) + batch_var = h.var(dim=0, unbiased=False).detach() + if not self.initialized: + self.running_var = batch_var + self.initialized = True + else: + self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var + + def __call__(self, x: torch.Tensor) -> torch.Tensor: + """Whiten x of shape (batch_size, T).""" + h = hartley_transform(x) + h_white = h / torch.sqrt(self.running_var + self.eps) + return hartley_transform(h_white) + + class DiagonalWhitener(torch.nn.Module): def __init__(self, dim, momentum=0.1, eps=1e-8, use_fourier=False, track_mean=True): """ diff --git a/falcon/embeddings/svd.py b/falcon/embeddings/svd.py index 9da80fb..f886953 100644 --- a/falcon/embeddings/svd.py +++ b/falcon/embeddings/svd.py @@ -3,6 +3,155 @@ from typing import Optional, List +class DynamicSVD(torch.nn.Module): + """ + Streaming SVD with Procrustes-stabilized output and optional whitening. + + Maintains eigenbasis (V, Λ) via momentum-blended, eigenvalue-scaled SVD + updates. Procrustes alignment ensures output coefficients have stable + meaning across updates — critical when feeding into a neural network. + + Optionally wraps a whitener (e.g. DiagonalWhitener). When a whitener is + provided, update(x, signal) computes noise = x - signal, updates the + whitener from the noise, then whitens x before the SVD update. forward() + applies whitening at inference without updating statistics. + + Update (when buffer full): + U = [ √(1-α) · diag(√Λ_old) · V_old ; √(α/M) · X_white ] + SVD(U) → V_new, Λ_new = S² + Procrustes(V_new, R_old @ V_old) → R_new + + forward(x) → stable k-dim coefficients: + 1. c = X_white @ V.T (project onto eigenbasis) + 2. c *= λ/(λ+1) (Wiener filter, diagonal) + 3. c /= √λ (normalize to ~unit variance) + 4. c_out = c @ R.T (rotate to stable frame) + """ + + def __init__( + self, + n_components: int = 10, + buffer_size: Optional[int] = None, + momentum: float = 0.1, + shrinkage: bool = True, + whitener=None, + ) -> None: + super().__init__() + self.n_components = n_components + self.buffer_size = buffer_size if buffer_size is not None else 4 * n_components + self.momentum = momentum + self.shrinkage = shrinkage + self.whitener = whitener + + self.buffer: List[torch.Tensor] = [] + self.buffer_counter: int = 0 + + self.components: Optional[torch.Tensor] = None # (k, D) + self.eigenvalues: Optional[torch.Tensor] = None # (k,) + self._R: Optional[torch.Tensor] = None # (k, k) + + def update(self, x: torch.Tensor, signal: Optional[torch.Tensor] = None) -> None: + """Accumulate a batch; trigger SVD update when buffer is full. + + Args: + x: Input data, shape (batch_size, D). + signal: True signal estimate, same shape as x. If provided and a + whitener is attached, noise = x - signal is used to update + the whitener before whitening x. + """ + if self.whitener is not None and signal is not None: + self.whitener.update((x - signal).detach()) + + x_white = self.whitener(x) if self.whitener is not None else x + + self.buffer.append(x_white) + self.buffer_counter += x_white.shape[0] + + if self.buffer_counter >= self.buffer_size: + self._svd_update() + self.buffer = [] + self.buffer_counter = 0 + + def _svd_update(self) -> None: + X = torch.cat(self.buffer, dim=0) + M = X.shape[0] + alpha = self.momentum + + if self.components is None: + K = X @ X.T / M + eigvals, eigvecs = torch.linalg.eigh(K) + idx = torch.argsort(eigvals, descending=True)[: self.n_components] + Q = eigvecs[:, idx] + Λ = eigvals[idx] + V = (X.T @ Q) / torch.sqrt(M * Λ.clamp(min=1e-12)) + self.components = V.T + self.eigenvalues = Λ + self._R = torch.eye(self.n_components, dtype=V.dtype, device=V.device) + else: + Λ_sqrt = torch.sqrt(self.eigenvalues.clamp(min=1e-12)) + scaled_old = np.sqrt(1 - alpha) * (Λ_sqrt.unsqueeze(1) * self.components) + scaled_new = np.sqrt(alpha / M) * X + U = torch.cat([scaled_old, scaled_new], dim=0) + _, S, Vt = torch.linalg.svd(U, full_matrices=False) + V_new = Vt[: self.n_components] + Λ_new = S[: self.n_components] ** 2 + + V_stable_old = self._R @ self.components + C = V_stable_old @ V_new.T + U2, _, Wt = torch.linalg.svd(C) + R_new = U2 @ Wt + + self.components = V_new + self.eigenvalues = Λ_new + self._R = R_new + + def forward(self, x: torch.Tensor, signal: Optional[torch.Tensor] = None) -> torch.Tensor: + """Project to stable k-dimensional coefficients. + + When in training mode, automatically accumulates x into the buffer and + triggers an SVD update when the buffer is full. This makes DynamicSVD + usable as a drop-in nn.Module embedding without a separate update() call. + + Args: + x: Input data, shape (batch_size, D). + signal: If provided and a whitener is attached, used to estimate + noise for whitener updates (passed through to update()). + + Returns: + Coefficients of shape (batch_size, k), ~unit variance. Returns + zeros before the first SVD update. + """ + if self.training: + with torch.no_grad(): + self.update(x.detach(), signal) + + if self.components is None: + return torch.randn(x.shape[0], self.n_components, dtype=x.dtype, device=x.device) + + x_white = self.whitener(x) if self.whitener is not None else x + c = x_white @ self.components.T + + if self.shrinkage and self.eigenvalues is not None: + Λ = self.eigenvalues.clamp(min=1e-12) + c = c * (Λ / (Λ + 1.0) / torch.sqrt(Λ)).unsqueeze(0) + + return c @ self._R.T + + def reconstruct(self, x: torch.Tensor) -> torch.Tensor: + """Wiener-filter and reconstruct in whitened D-dimensional space.""" + if self.components is None: + raise ValueError("Call update() enough times before reconstruct().") + + x_white = self.whitener(x) if self.whitener is not None else x + x_proj = x_white @ self.components.T + + if self.shrinkage: + shrink = self.eigenvalues / (self.eigenvalues + 1.0) + x_proj = x_proj * shrink.unsqueeze(0) + + return x_proj @ self.components + + class PCAProjector(torch.nn.Module): """ A streaming, dual PCA projector with momentum-based updates. diff --git a/falcon/estimators/__init__.py b/falcon/estimators/__init__.py index cef0d22..01b1300 100644 --- a/falcon/estimators/__init__.py +++ b/falcon/estimators/__init__.py @@ -12,19 +12,12 @@ TrainingLoopConfig, ) from falcon.estimators.gaussian import Gaussian -from falcon.estimators.gaussian_fullcov import ( - GaussianConfig, - GaussianFullCov, - GaussianPosterior, -) +from falcon.estimators.gaussian_fullcov import GaussianFullCov __all__ = [ "Flow", - "FlowConfig", "Gaussian", - "GaussianConfig", "GaussianFullCov", - "GaussianPosterior", "StepwiseEstimator", "LossBasedEstimator", "TrainingLoopConfig", @@ -33,7 +26,6 @@ # Lazy imports for sbi-dependent classes _LAZY_IMPORTS = { "Flow": "falcon.estimators.flow", - "FlowConfig": "falcon.estimators.flow", } diff --git a/falcon/estimators/flow.py b/falcon/estimators/flow.py index f4ca9a5..a8ba86a 100644 --- a/falcon/estimators/flow.py +++ b/falcon/estimators/flow.py @@ -2,125 +2,131 @@ import copy import time -from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional import numpy as np import torch -from omegaconf import OmegaConf from torch.optim import AdamW from torch.optim.lr_scheduler import ReduceLROnPlateau -from falcon.core.logger import log, debug, info, warning, error +from falcon.core.logger import log, debug from falcon.estimators.flow_density import FlowDensity -from falcon.estimators.stepwise_base import StepwiseEstimator, TrainingLoopConfig +from falcon.estimators.stepwise_base import StepwiseEstimator from falcon.embeddings import instantiate_embedding -# ==================== Configuration Dataclasses ==================== - - -@dataclass -class NetworkConfig: - """Neural network architecture parameters.""" - - net_type: str = "zuko_nice" - theta_norm: bool = True - norm_momentum: float = 1e-2 - adaptive_momentum: bool = False - use_log_update: bool = False - - -@dataclass -class OptimizerConfig: - """Optimizer parameters (training-time).""" - - lr: float = 1e-2 - betas: tuple = (0.9, 0.9) # Lower beta2 for dynamic SBI setting - lr_decay_factor: float = 0.1 - scheduler_patience: int = 8 - - -@dataclass -class InferenceConfig: - """Inference and sampling parameters.""" - - gamma: float = 0.5 - discard_samples: bool = True - log_ratio_threshold: float = -20.0 - sample_reference_posterior: bool = False - use_best_models_during_inference: bool = True - # Importance sampling parameters - num_proposals: int = 256 - reference_samples: int = 128 - hypercube_bound: float = 2.0 - out_of_bounds_penalty: float = 100.0 - nan_replacement: float = -100.0 - - -@dataclass -class FlowConfig: - """Top-level Flow estimator configuration.""" - - loop: TrainingLoopConfig = field(default_factory=TrainingLoopConfig) - network: NetworkConfig = field(default_factory=NetworkConfig) - optimizer: OptimizerConfig = field(default_factory=OptimizerConfig) - inference: InferenceConfig = field(default_factory=InferenceConfig) - embedding: Optional[Any] = None - device: Optional[str] = None - - -# ==================== Flow Implementation ==================== - - class Flow(StepwiseEstimator): - """ - Flow-based posterior estimation (formerly SNPE_A). - - Implementation-specific features: - - Dual flow architecture (conditional + marginal) - - Parameter space normalization via hypercube mapping - - Importance sampling for posterior/proposal + """Flow-based posterior estimation using a conditional + marginal flow pair. + + Args: + max_epochs: Maximum training epochs. + net_type: Flow architecture (``zuko_nice``, ``nsf``, ``maf``, ``zuko_gf``, ...). + lr: Learning rate. + gamma: Proposal tempering coefficient. + embedding: Embedding config dict (with ``_target_`` etc.) or ``None``. + device: Device string (e.g. ``"cuda:0"``); auto-detected if ``None``. + batch_size: Mini-batch size. + early_stop_patience: Epochs without improvement before stopping. + prior_epochs: Epochs to sample from prior before switching to proposal. + cache_on_device: Cache training data on the estimator device. + cache_sync_every: Resync buffer cache every N epochs (0 = every epoch). + max_cache_samples: Cap on cached training samples (0 = all). + theta_norm: Normalise parameter space online. + norm_momentum: EMA momentum for online normalisation. + adaptive_momentum: Adaptive momentum for normalisation. + use_log_update: Use log-space normalisation update. + betas: AdamW beta coefficients. + lr_decay_factor: LR decay factor for plateau scheduler. + lr_patience: Plateau patience before LR decay. + discard_samples: Discard low log-ratio training samples. + log_ratio_threshold: Log-ratio cutoff for discarding. + sample_reference_posterior: Sample reference posterior for proposals. + use_best_models: Use best-checkpoint networks for sampling. + num_proposals: Importance sampling proposal count. + reference_samples: Reference posterior sample count. + hypercube_bound: Hypercube clipping bound for proposals. + out_of_bounds_penalty: Log-weight penalty for out-of-bounds samples. + nan_replacement: Replacement for NaN/−∞ log-weights. """ def __init__( self, - simulator_instance, - theta_key: Optional[str] = None, - condition_keys: Optional[List[str]] = None, - config: Optional[dict] = None, + *, + # Most commonly changed + max_epochs: int = 100, + net_type: str = "zuko_nice", + lr: float = 1e-2, + gamma: float = 0.5, + embedding=None, + device: Optional[str] = None, + # Training loop + batch_size: int = 128, + early_stop_patience: int = 16, + prior_epochs: int = 0, + cache_on_device: bool = False, + cache_sync_every: int = 0, + max_cache_samples: int = 0, + # Network + theta_norm: bool = True, + norm_momentum: float = 1e-2, + adaptive_momentum: bool = False, + use_log_update: bool = False, + # Optimizer + betas: tuple = (0.9, 0.9), + lr_decay_factor: float = 0.1, + lr_patience: int = 8, + # Inference + discard_samples: bool = True, + log_ratio_threshold: float = -20.0, + sample_reference_posterior: bool = False, + use_best_models: bool = True, + num_proposals: int = 256, + reference_samples: int = 128, + hypercube_bound: float = 2.0, + out_of_bounds_penalty: float = 100.0, + nan_replacement: float = -100.0, ): - """ - Initialize Flow estimator. - - Args: - simulator_instance: Prior/simulator instance - theta_key: Key for theta in batch data - condition_keys: Keys for condition data in batch - config: Configuration dict with loop, network, optimizer, inference sections - """ - # Merge user config with defaults using OmegaConf structured config - schema = OmegaConf.structured(FlowConfig) - config = OmegaConf.merge(schema, config or {}) - - super().__init__( - simulator_instance=simulator_instance, - loop_config=config.loop, - theta_key=theta_key, - condition_keys=condition_keys, - ) - - self.config = config - - # Device setup - self.device = self._setup_device(config.device) + self.max_epochs = max_epochs + self.net_type = net_type + self.lr = lr + self.gamma = gamma + self.embedding = embedding + self.device = device + self.batch_size = batch_size + self.early_stop_patience = early_stop_patience + self.prior_epochs = prior_epochs + self.cache_on_device = cache_on_device + self.cache_sync_every = cache_sync_every + self.max_cache_samples = max_cache_samples + self.theta_norm = theta_norm + self.norm_momentum = norm_momentum + self.adaptive_momentum = adaptive_momentum + self.use_log_update = use_log_update + self.betas = betas + self.lr_decay_factor = lr_decay_factor + self.lr_patience = lr_patience + self.discard_samples = discard_samples + self.log_ratio_threshold = log_ratio_threshold + self.sample_reference_posterior = sample_reference_posterior + self.use_best_models = use_best_models + self.num_proposals = num_proposals + self.reference_samples = reference_samples + self.hypercube_bound = hypercube_bound + self.out_of_bounds_penalty = out_of_bounds_penalty + self.nan_replacement = nan_replacement + + def setup(self, simulator_instance, theta_key=None, condition_keys=None): + super().setup(simulator_instance, theta_key, condition_keys) + + if self.device: + self.device = torch.device(self.device) + else: + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + debug(f"Auto-detected device: {self.device}") - # Embedding network - embedding_config = OmegaConf.to_container(config.embedding, resolve=True) - self._embedding = instantiate_embedding(embedding_config).to(self.device) + self._embedding = instantiate_embedding(self.embedding).to(self.device) - # Flow networks (initialized lazily) self._conditional_flow = None self._marginal_flow = None self._best_conditional_flow = None @@ -128,52 +134,30 @@ def __init__( self._best_embedding = None self._init_parameters = None - # Best loss tracking self.best_conditional_flow_val_loss = float("inf") self.best_marginal_flow_val_loss = float("inf") - # Optimizer/scheduler (initialized lazily) self._optimizer = None self._scheduler = None - # Extended history for Flow-specific tracking - self.history.update({ - "theta_mins": [], - "theta_maxs": [], - }) - - def _setup_device(self, device: Optional[str]) -> torch.device: - """Setup compute device.""" - if device: - return torch.device(device) - dev = torch.device("cuda" if torch.cuda.is_available() else "cpu") - debug(f"Auto-detected device: {dev}") - return dev + self.history.update({"theta_mins": [], "theta_maxs": []}) # ==================== Network Initialization ==================== def _initialize_networks(self, theta: torch.Tensor, conditions: Dict) -> None: - """Initialize flow networks and optimizer.""" - self._init_parameters = [theta, conditions] debug("Initializing networks...") - debug(f"GPU available: {torch.cuda.is_available()}") - - cfg_net = self.config.network - cfg_opt = self.config.optimizer + self._init_parameters = [theta, conditions] - # Embed conditions to get embedding dimension conditions_device = {k: v.to(self.device) for k, v in conditions.items()} s = self._embed(conditions_device, train=False).detach() theta_device = theta.to(self.device) - # Create flow networks self._conditional_flow = self._create_flow(theta_device, s, is_conditional=True) self._conditional_flow.to(self.device) self._marginal_flow = self._create_flow(theta_device, s, is_conditional=False) self._marginal_flow.to(self.device) - # Best-fit copies self._best_conditional_flow = self._create_flow(theta_device, s, is_conditional=True) self._best_conditional_flow.to(self.device) self._best_conditional_flow.load_state_dict(self._conditional_flow.state_dict()) @@ -184,82 +168,57 @@ def _initialize_networks(self, theta: torch.Tensor, conditions: Dict) -> None: self._best_embedding = copy.deepcopy(self._embedding) - # Optimizer and scheduler parameters = ( list(self._conditional_flow.parameters()) + list(self._marginal_flow.parameters()) + list(self._embedding.parameters()) ) - self._optimizer = AdamW(parameters, lr=cfg_opt.lr, betas=cfg_opt.betas) + self._optimizer = AdamW(parameters, lr=self.lr, betas=self.betas) self._scheduler = ReduceLROnPlateau( self._optimizer, mode="min", - factor=cfg_opt.lr_decay_factor, - patience=cfg_opt.scheduler_patience, + factor=self.lr_decay_factor, + patience=self.lr_patience, ) self.networks_initialized = True debug("Networks initialized.") def _create_flow(self, theta, s, is_conditional=True): - """Create a FlowDensity network with current config.""" - cfg = self.config.network return FlowDensity( theta, s if is_conditional else s * 0, - theta_norm=cfg.theta_norm, - norm_momentum=cfg.norm_momentum, - net_type=cfg.net_type, - use_log_update=cfg.use_log_update, - adaptive_momentum=cfg.adaptive_momentum, + theta_norm=self.theta_norm, + norm_momentum=self.norm_momentum, + net_type=self.net_type, + use_log_update=self.use_log_update, + adaptive_momentum=self.adaptive_momentum, ) # ==================== Train/Val Steps ==================== def _unpack_batch(self, batch, phase: str): - """Unpack batch data and convert to tensors. - - Args: - batch: Batch object with theta, logprob, and conditions - phase: "train" or "val" for logging and history - - Returns: - Tuple of (ids, theta, theta_logprob, conditions, u, u_device, conditions_device) - """ ids = batch._ids theta = self._to_tensor(batch[f"{self.theta_key}.value"]) theta_logprob = self._to_tensor(batch[f"{self.theta_key}.log_prob"]) conditions = { - k: self._to_tensor(batch[f"{k}.value"]) for k in self.condition_keys if f"{k}.value" in batch + k: self._to_tensor(batch[f"{k}.value"]) + for k in self.condition_keys if f"{k}.value" in batch } - # Record IDs for history ts = time.time() self.history[f"{phase}_ids"].extend((ts, id) for id in ids.tolist()) log({f"{phase}:theta_logprob_min": theta_logprob.min().item()}) log({f"{phase}:theta_logprob_max": theta_logprob.max().item()}) - # Transform to hypercube space u = self.simulator_instance.inverse(theta) - - # Move to device conditions_device = {k: v.to(self.device) for k, v in conditions.items()} u_device = u.to(self.device) return ids, theta, theta_logprob, conditions, u, u_device, conditions_device def _compute_flow_losses(self, u_device, s, train: bool): - """Compute conditional and marginal flow losses. - - Args: - u_device: Transformed parameters on device - s: Embedded conditions - train: Whether in training mode - - Returns: - Tuple of (loss_cond, loss_marg) tensors - """ if train: self._conditional_flow.train() self._marginal_flow.train() @@ -268,122 +227,92 @@ def _compute_flow_losses(self, u_device, s, train: bool): self._marginal_flow.eval() loss_cond = self._conditional_flow.loss(u_device, s).mean() - # Zero out conditions for marginal flow (detach in train mode to avoid backprop) s_marginal = s.detach() * 0 if train else s * 0 loss_marg = self._marginal_flow.loss(u_device, s_marginal).mean() return loss_cond, loss_marg def train_step(self, batch) -> Dict[str, float]: - """Flow training step with gradient update and optional sample discarding.""" ids, theta, theta_logprob, conditions, u, u_device, conditions_device = \ self._unpack_batch(batch, "train") - # Initialize networks on first batch if not self.networks_initialized: self._initialize_networks(u, conditions) - # Embed conditions s = self._embed(conditions_device, train=True) - # Track theta ranges with torch.no_grad(): self.history["theta_mins"].append(theta.min(dim=0).values.cpu().numpy()) self.history["theta_maxs"].append(theta.max(dim=0).values.cpu().numpy()) - # Forward and backward pass self._optimizer.zero_grad() loss_cond, loss_marg = self._compute_flow_losses(u_device, s, train=True) (loss_cond + loss_marg).backward() self._optimizer.step() - # Discard samples based on log-likelihood ratio - if self.config.inference.discard_samples: + if self.discard_samples: discard_mask = self._compute_discard_mask(theta, theta_logprob, conditions_device) batch.discard(discard_mask) return {"loss": loss_cond.item(), "loss_aux": loss_marg.item()} def val_step(self, batch) -> Dict[str, float]: - """Flow validation step without gradient computation.""" _, theta, theta_logprob, conditions, u, u_device, conditions_device = \ self._unpack_batch(batch, "val") - # Embed conditions (eval mode) s = self._embed(conditions_device, train=False) - # Compute losses without gradients with torch.no_grad(): loss_cond, loss_marg = self._compute_flow_losses(u_device, s, train=False) return {"loss": loss_cond.item(), "loss_aux": loss_marg.item()} def on_epoch_end(self, epoch: int, val_metrics: Dict[str, float]) -> Optional[Dict[str, float]]: - """Update best weights and scheduler.""" val_loss = val_metrics.get("loss", float("inf")) val_aux_loss = val_metrics.get("loss_aux", float("inf")) - # Update best conditional flow if val_loss < self.best_conditional_flow_val_loss: self.best_conditional_flow_val_loss = val_loss self._update_best_weights("conditional") log({"checkpoint:conditional": epoch}) - # Update best marginal flow if val_aux_loss < self.best_marginal_flow_val_loss: self.best_marginal_flow_val_loss = val_aux_loss self._update_best_weights("marginal") log({"checkpoint:marginal": epoch}) - # LR scheduler step self._scheduler.step(val_loss) lr = self._optimizer.param_groups[0]["lr"] log({"lr": lr}) return {"lr": lr} - # ==================== Sampling Methods ==================== + # ==================== Sampling ==================== - def sample_prior(self, num_samples: int, conditions: Optional[Dict] = None) -> dict: - """Sample from the prior distribution.""" + def sample_prior(self, num_samples: int, conditions=None) -> dict: if conditions: raise ValueError("Conditions are not supported for sample_prior.") samples = self.simulator_instance.simulate_batch(num_samples) - # Log probability for uniform prior over hypercube [-bound, bound]^d - bound = self.config.inference.hypercube_bound - log_prob = np.ones(num_samples) * (-np.log(2 * bound) ** self.param_dim) + log_prob = np.ones(num_samples) * (-np.log(2 * self.hypercube_bound) ** self.param_dim) return {'value': samples, 'log_prob': log_prob} - def sample_posterior( - self, - num_samples: int, - conditions: Optional[Dict] = None, - ) -> dict: - """Sample from the posterior distribution q(theta|x).""" - # Fall back to prior if networks not yet initialized (training hasn't started) + def sample_posterior(self, num_samples: int, conditions=None) -> dict: if not self.networks_initialized: return self.sample_prior(num_samples) - samples, logprob = self._importance_sample(num_samples, mode="posterior", conditions=conditions or {}) return {'value': samples.numpy(), 'log_prob': logprob.numpy()} - def sample_proposal( - self, - num_samples: int, - conditions: Optional[Dict] = None, - ) -> dict: - """Sample from the widened proposal distribution for adaptive resampling.""" - if self._total_epochs_trained < self.loop_config.prior_epochs: + def sample_proposal(self, num_samples: int, conditions=None) -> dict: + if self._total_epochs_trained < self.prior_epochs: return self.sample_prior(num_samples) - # Fall back to prior if networks not yet initialized (training hasn't started) if not self.networks_initialized: return self.sample_prior(num_samples) - cfg_inf = self.config.inference conditions = conditions or {} - - if cfg_inf.sample_reference_posterior: - post_samples, _ = self._importance_sample(cfg_inf.reference_samples, mode="posterior", conditions=conditions) + if self.sample_reference_posterior: + post_samples, _ = self._importance_sample( + self.reference_samples, mode="posterior", conditions=conditions + ) mean, std = post_samples.mean(dim=0).cpu(), post_samples.std(dim=0).cpu() log({f"sample_proposal:posterior_mean_{i}": mean[i].item() for i in range(len(mean))}) log({f"sample_proposal:posterior_std_{i}": std[i].item() for i in range(len(std))}) @@ -396,21 +325,11 @@ def sample_proposal( }) return {'value': samples.numpy(), 'log_prob': logprob.numpy()} - def _importance_sample( - self, - num_samples: int, - mode: str = "posterior", - conditions: Dict = {}, - ): - """Sample using importance sampling.""" - cfg_inf = self.config.inference - + def _importance_sample(self, num_samples: int, mode: str = "posterior", conditions: Dict = {}): assert conditions, "Conditions must be provided." - # Move conditions to device (handles both numpy arrays and tensors) conditions = {k: self._to_tensor(v, self.device) for k, v in conditions.items()} - # Use best models if available and configured, otherwise fall back to current - use_best = cfg_inf.use_best_models_during_inference and self._best_conditional_flow is not None + use_best = self.use_best_models and self._best_conditional_flow is not None if use_best: conditional_net = self._best_conditional_flow marginal_net = self._best_marginal_flow @@ -422,42 +341,34 @@ def _importance_sample( s = s.expand(num_samples, *s.shape[1:]) - # Generate proposals from conditional flow conditional_net.eval() - samples_proposals = conditional_net.sample(cfg_inf.num_proposals, s).detach() + samples_proposals = conditional_net.sample(self.num_proposals, s).detach() log({ "importance_sample:proposal_mean": samples_proposals.mean().item(), "importance_sample:proposal_std": samples_proposals.std().item(), }) - # Compute log probs log_prob_cond = conditional_net.log_prob(samples_proposals, s) marginal_net.eval() log_prob_marg = marginal_net.log_prob(samples_proposals, s * 0) - # Mask samples outside hypercube bounds - bound = cfg_inf.hypercube_bound - mask = (samples_proposals < -bound) | (samples_proposals > bound) - mask = mask.any(dim=-1).float() * cfg_inf.out_of_bounds_penalty + mask = (samples_proposals < -self.hypercube_bound) | (samples_proposals > self.hypercube_bound) + mask = mask.any(dim=-1).float() * self.out_of_bounds_penalty - # Compute importance weights if mode == "proposal": - log_weights = -1.0 / (1.0 + cfg_inf.gamma) * log_prob_cond - mask - else: # "posterior" - reweight by marginal + log_weights = -1.0 / (1.0 + self.gamma) * log_prob_cond - mask + else: log_weights = -log_prob_marg - mask - nan_val = cfg_inf.nan_replacement - log_weights = torch.nan_to_num(log_weights, nan=nan_val, neginf=nan_val) + log_weights = torch.nan_to_num(log_weights, nan=self.nan_replacement, neginf=self.nan_replacement) log_weights = log_weights - torch.logsumexp(log_weights, dim=0, keepdim=True) weights = torch.exp(log_weights) - # Effective sample size n_eff = 1 / (weights**2).sum(dim=0).cpu().detach().numpy() log({"importance_sample:n_eff_min": n_eff.min()}) log({"importance_sample:n_eff_max": n_eff.max()}) - # Resample idx = torch.multinomial(weights.T, 1, replacement=True).squeeze(-1) samples = samples_proposals[idx, torch.arange(num_samples), :] samples = self.simulator_instance.forward(samples).cpu() @@ -468,7 +379,6 @@ def _importance_sample( # ==================== Save/Load ==================== def save(self, node_dir: Path) -> None: - """Save Flow state.""" debug(f"Saving: {node_dir}") if not self.networks_initialized: raise RuntimeError("Networks not initialized.") @@ -478,7 +388,6 @@ def save(self, node_dir: Path) -> None: torch.save(self._init_parameters, node_dir / "init_parameters.pth") torch.save(self._total_epochs_trained, node_dir / "total_epochs_trained.pth") - # Save history torch.save(self.history["train_ids"], node_dir / "train_id_history.pth") torch.save(self.history["val_ids"], node_dir / "validation_id_history.pth") torch.save(self.history["theta_mins"], node_dir / "theta_mins_batches.pth") @@ -493,17 +402,12 @@ def save(self, node_dir: Path) -> None: torch.save(self._best_embedding.state_dict(), node_dir / "embedding.pth") def load(self, node_dir: Path) -> None: - """Load Flow state.""" debug(f"Loading: {node_dir}") init_parameters = torch.load(node_dir / "init_parameters.pth") self._initialize_networks(init_parameters[0], init_parameters[1]) - self._best_conditional_flow.load_state_dict( - torch.load(node_dir / "conditional_flow.pth") - ) - self._best_marginal_flow.load_state_dict( - torch.load(node_dir / "marginal_flow.pth") - ) + self._best_conditional_flow.load_state_dict(torch.load(node_dir / "conditional_flow.pth")) + self._best_marginal_flow.load_state_dict(torch.load(node_dir / "marginal_flow.pth")) if (node_dir / "embedding.pth").exists() and self._best_embedding is not None: self._best_embedding.load_state_dict(torch.load(node_dir / "embedding.pth")) @@ -514,17 +418,14 @@ def load(self, node_dir: Path) -> None: # ==================== Private Helpers ==================== def _embed(self, conditions: Dict, train: bool = True, use_best_fit: bool = False): - """Run conditions through embedding network.""" embedding = ( - self._best_embedding - if use_best_fit and self._best_embedding is not None + self._best_embedding if use_best_fit and self._best_embedding is not None else self._embedding ) embedding.train() if train else embedding.eval() return embedding(conditions) def _update_best_weights(self, network_type: str) -> None: - """Copy current network weights to best-fit checkpoint.""" if network_type == "conditional": self._best_conditional_flow.load_state_dict( {k: v.clone() for k, v in self._conditional_flow.state_dict().items()} @@ -537,12 +438,7 @@ def _update_best_weights(self, network_type: str) -> None: {k: v.clone() for k, v in self._marginal_flow.state_dict().items()} ) - def _compute_discard_mask( - self, theta: torch.Tensor, theta_logprob: torch.Tensor, conditions: Dict - ): - """Compute boolean mask of samples to discard based on log-likelihood ratio.""" - cfg_inf = self.config.inference - + def _compute_discard_mask(self, theta, theta_logprob, conditions): u = self.simulator_instance.inverse(theta) s = self._embed(conditions, train=False, use_best_fit=True) @@ -553,4 +449,4 @@ def _compute_discard_mask( self._conditional_flow.eval() log_prob = self._conditional_flow.log_prob(u.unsqueeze(0), s).squeeze(0).cpu() log_ratio = log_prob - theta_logprob.cpu() - return log_ratio < cfg_inf.log_ratio_threshold + return log_ratio < self.log_ratio_threshold diff --git a/falcon/estimators/gaussian.py b/falcon/estimators/gaussian.py index 9080916..ae29a2c 100644 --- a/falcon/estimators/gaussian.py +++ b/falcon/estimators/gaussian.py @@ -1,79 +1,28 @@ -"""Gaussian posterior estimation — Gaussian factory for LossBasedEstimator.""" +"""Gaussian posterior estimation — deprecated factory wrapper. -from typing import List, Optional - -from omegaconf import OmegaConf - -from falcon.priors.product import TransformedPrior -from falcon.estimators.stepwise_base import LossBasedEstimator -from falcon.estimators.gaussian_fullcov import GaussianConfig, GaussianPosterior # noqa: F401 +Use ``falcon.estimators.GaussianFullCov`` directly instead. +""" - -# ==================== Factory Function ==================== +import warnings +from typing import List, Optional def Gaussian( simulator_instance, theta_key: Optional[str] = None, condition_keys: Optional[List[str]] = None, - config: Optional[dict] = None, -) -> LossBasedEstimator: - """Create a LossBasedEstimator with GaussianPosterior. - - This is the main entry point for using Gaussian posterior estimation. - It provides sensible defaults while allowing full customization. +): + """Create a GaussianFullCov estimator (deprecated factory). - Args: - simulator_instance: Prior/simulator instance - theta_key: Key for theta in batch data - condition_keys: Keys for condition data in batch - config: Configuration dict with sections: - - loop: TrainingLoopConfig options - - network: NetworkConfig options - - optimizer: OptimizerConfig options - - inference: InferenceConfig options - - embedding: Embedding configuration with _target_ - - device: Device string (optional) - - Returns: - Configured LossBasedEstimator ready for training - - Example YAML: - estimator: - _target_: falcon.estimators.Gaussian - network: - hidden_dim: 128 - num_layers: 3 - embedding: - _target_: model.E - _input_: [x] + .. deprecated:: + Use :class:`falcon.estimators.GaussianFullCov` directly. """ - # Check simulator supports transformation interface - if not isinstance(simulator_instance, TransformedPrior): - raise TypeError( - f"Gaussian requires a TransformedPrior (e.g., Product), " - f"got {type(simulator_instance).__name__}. " - f"The simulator must support forward/inverse with mode='standard_normal'." - ) - - # Merge with defaults - schema = OmegaConf.structured(GaussianConfig) - cfg = OmegaConf.merge(schema, config or {}) - - # Extract configs as plain dicts - embedding_config = OmegaConf.to_container(cfg.embedding, resolve=True) - posterior_config = OmegaConf.to_container(cfg.network, resolve=True) - - return LossBasedEstimator( - simulator_instance=simulator_instance, - posterior_cls=GaussianPosterior, - embedding_config=embedding_config, - loop_config=cfg.loop, - optimizer_config=cfg.optimizer, - inference_config=cfg.inference, - posterior_config=posterior_config, - theta_key=theta_key, - condition_keys=condition_keys, - device=cfg.device, - latent_mode="standard_normal", # GaussianPosterior assumes N(0,I) prior + warnings.warn( + "falcon.estimators.Gaussian is deprecated; use GaussianFullCov directly.", + DeprecationWarning, + stacklevel=2, ) + from falcon.estimators.gaussian_fullcov import GaussianFullCov + est = GaussianFullCov() + est.setup(simulator_instance, theta_key, condition_keys) + return est diff --git a/falcon/estimators/gaussian_fullcov.py b/falcon/estimators/gaussian_fullcov.py index 945930c..bd554a3 100644 --- a/falcon/estimators/gaussian_fullcov.py +++ b/falcon/estimators/gaussian_fullcov.py @@ -2,72 +2,25 @@ import copy import time -from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional import numpy as np import torch import torch.nn as nn -from omegaconf import OmegaConf from torch.optim import AdamW from torch.optim.lr_scheduler import ReduceLROnPlateau from falcon.priors.product import TransformedPrior from falcon.estimators.networks import build_mlp -from falcon.estimators.stepwise_base import StepwiseEstimator, TrainingLoopConfig +from falcon.estimators.stepwise_base import StepwiseEstimator from falcon.core.logger import log, debug -# ==================== Configuration Dataclasses ==================== +# ==================== _GaussianPosterior Module ==================== -@dataclass -class NetworkConfig: - """Configuration for GaussianPosterior network.""" - - hidden_dim: int = 128 - num_layers: int = 3 - momentum: float = 0.01 - min_var: float = 1e-20 - eig_update_freq: int = 1 - - -@dataclass -class OptimizerConfig: - """Optimizer and scheduler parameters.""" - - lr: float = 1e-2 - betas: tuple = (0.9, 0.9) # Lower beta2 for dynamic SBI setting - lr_decay_factor: float = 1.0 # 1.0 = no LR decay (scheduler disabled) - scheduler_patience: int = 8 - - -@dataclass -class InferenceConfig: - """Inference and sampling parameters.""" - - gamma: float = 0.5 - discard_samples: bool = False - log_ratio_threshold: float = -20.0 - - -@dataclass -class GaussianConfig: - """Top-level Gaussian estimator configuration.""" - - loop: TrainingLoopConfig = field(default_factory=TrainingLoopConfig) - network: NetworkConfig = field(default_factory=NetworkConfig) - optimizer: OptimizerConfig = field(default_factory=OptimizerConfig) - inference: InferenceConfig = field(default_factory=InferenceConfig) - embedding: Optional[Any] = None - device: Optional[str] = None - - -# ==================== GaussianPosterior Module ==================== - - -class GaussianPosterior(nn.Module): +class _GaussianPosterior(nn.Module): """Full covariance Gaussian posterior with eigenvalue-based operations. Implements the Posterior contract: @@ -260,21 +213,90 @@ def _update_eigendecomp(self) -> None: class GaussianFullCov(StepwiseEstimator): """Full-covariance Gaussian posterior estimator for TransformedPrior simulators. - Works in the standard-normal latent space defined by the simulator's - forward/inverse transforms. Samples are mapped back to parameter space - after generation. - - Gamma is resolved once at initialisation: - - proposal sampling uses _proposal_gamma (widens the distribution) - - posterior sampling uses _posterior_gamma (corrects for proposal bias) + Works in the standard-normal latent space; samples are mapped back to + parameter space after generation. + + Args: + max_epochs: Maximum training epochs. + lr: Learning rate. + gamma: Proposal tempering coefficient. + embedding: Embedding config dict or ``None``. + device: Device string; auto-detected if ``None``. + batch_size: Mini-batch size. + early_stop_patience: Epochs without improvement before stopping. + prior_epochs: Epochs to sample from prior before switching to proposal. + cache_on_device: Cache training data on the estimator's device. + cache_sync_every: Resync buffer cache every N epochs (0 = every epoch). + max_cache_samples: Cap on cached training samples (0 = all). + hidden_dim: MLP hidden layer width. + num_layers: MLP depth. + momentum: EMA momentum for running statistics. + min_var: Minimum variance for numerical stability. + eig_update_freq: Eigendecomposition update frequency. + betas: AdamW beta coefficients. + lr_decay_factor: LR decay factor (1.0 = no decay). + lr_patience: Plateau patience before LR decay. + discard_samples: Discard low log-ratio training samples. + log_ratio_threshold: Log-ratio cutoff for discarding. """ def __init__( + self, + *, + # Most commonly changed + max_epochs: int = 100, + lr: float = 1e-2, + gamma: float = 0.5, + embedding=None, + device=None, + # Training loop + batch_size: int = 128, + early_stop_patience: int = 16, + prior_epochs: int = 0, + cache_on_device: bool = False, + cache_sync_every: int = 0, + max_cache_samples: int = 0, + # Network architecture + hidden_dim: int = 128, + num_layers: int = 3, + momentum: float = 0.01, + min_var: float = 1e-20, + eig_update_freq: int = 1, + # Optimizer + betas: tuple = (0.9, 0.9), + lr_decay_factor: float = 1.0, + lr_patience: int = 8, + # Inference / sampling + discard_samples: bool = False, + log_ratio_threshold: float = -20.0, + ): + self.max_epochs = max_epochs + self.lr = lr + self.gamma = gamma + self.embedding = embedding + self.device = device + self.batch_size = batch_size + self.early_stop_patience = early_stop_patience + self.prior_epochs = prior_epochs + self.cache_on_device = cache_on_device + self.cache_sync_every = cache_sync_every + self.max_cache_samples = max_cache_samples + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.momentum = momentum + self.min_var = min_var + self.eig_update_freq = eig_update_freq + self.betas = betas + self.lr_decay_factor = lr_decay_factor + self.lr_patience = lr_patience + self.discard_samples = discard_samples + self.log_ratio_threshold = log_ratio_threshold + + def setup( self, simulator_instance, theta_key: Optional[str] = None, condition_keys: Optional[List[str]] = None, - config: Optional[dict] = None, ): if not isinstance(simulator_instance, TransformedPrior): raise TypeError( @@ -282,30 +304,20 @@ def __init__( f"got {type(simulator_instance).__name__}." ) - schema = OmegaConf.structured(GaussianConfig) - cfg = OmegaConf.merge(schema, config or {}) - - super().__init__( - simulator_instance=simulator_instance, - loop_config=cfg.loop, - theta_key=theta_key, - condition_keys=condition_keys, - ) - - self.cfg = cfg + super().setup(simulator_instance, theta_key, condition_keys) - if cfg.device: - self.device = torch.device(cfg.device) + if self.device: + self.device = torch.device(self.device) else: self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") debug(f"Auto-detected device: {self.device}") - # Resolve gamma once: proposal widens, posterior corrects back - gamma = cfg.inference.gamma - self._proposal_gamma = gamma - self._posterior_gamma = (1.0 + gamma) / gamma if gamma is not None else None + self._proposal_gamma = self.gamma + self._posterior_gamma = ( + (1.0 + self.gamma) / self.gamma + if self.gamma is not None else None + ) - # Model state (initialised lazily on first batch) self._model: Optional[nn.Module] = None self._best_model: Optional[nn.Module] = None self._best_loss: float = float("inf") @@ -317,9 +329,6 @@ def __init__( # ==================== Model Building ==================== def _build_model(self, batch) -> nn.Module: - from falcon.estimators.embedded_posterior import EmbeddedPosterior - from falcon.embeddings import instantiate_embedding - theta = self._to_tensor(batch[f"{self.theta_key}.value"]) conditions = { k: self._to_tensor(batch[f"{k}.value"]) @@ -335,18 +344,20 @@ def _create_model(self, theta: torch.Tensor, conditions: Dict[str, torch.Tensor] theta_latent = self.simulator_instance.inverse(theta, mode="standard_normal") - embedding_config = OmegaConf.to_container(self.cfg.embedding, resolve=True) - embedding = instantiate_embedding(embedding_config).to(self.device) + embedding = instantiate_embedding(self.embedding).to(self.device) embedding.eval() with torch.no_grad(): conditions_device = {k: v.to(self.device) for k, v in conditions.items()} embedded = embedding(conditions_device) - network_config = OmegaConf.to_container(self.cfg.network, resolve=True) - posterior = GaussianPosterior( + posterior = _GaussianPosterior( param_dim=theta_latent.shape[1], condition_dim=embedded.shape[1], - **network_config, + hidden_dim=self.hidden_dim, + num_layers=self.num_layers, + momentum=self.momentum, + min_var=self.min_var, + eig_update_freq=self.eig_update_freq, ).to(self.device) debug(f"GaussianFullCov model built: param_dim={theta_latent.shape[1]}") @@ -359,20 +370,37 @@ def _initialize_model(self, batch) -> None: {k: v.clone() for k, v in self._model.state_dict().items()} ) - opt_cfg = self.cfg.optimizer - self._optimizer = AdamW(self._model.parameters(), lr=opt_cfg.lr, betas=opt_cfg.betas) + self._optimizer = AdamW( + self._model.parameters(), + lr=self.lr, + betas=self.betas, + ) self._scheduler = ( ReduceLROnPlateau( self._optimizer, mode="min", - factor=opt_cfg.lr_decay_factor, - patience=opt_cfg.scheduler_patience, + factor=self.lr_decay_factor, + patience=self.lr_patience, ) - if opt_cfg.lr_decay_factor < 1.0 else None + if self.lr_decay_factor < 1.0 else None ) self.networks_initialized = True debug("GaussianFullCov initialised.") + def _sync_embedding_to_best(self) -> None: + """Copy DynamicSVD plain-attr state from _model to _best_model. + + DynamicSVD components/eigenvalues/_R are not registered buffers so they + are invisible to load_state_dict. This copies them alongside the network + weights so _best_model always reflects the full state at the best epoch. + """ + from falcon.embeddings.svd import DynamicSVD + for src, dst in zip(self._model.modules(), self._best_model.modules()): + if isinstance(src, DynamicSVD) and isinstance(dst, DynamicSVD): + for attr in ('components', 'eigenvalues', '_R'): + val = getattr(src, attr) + setattr(dst, attr, val.clone() if val is not None else None) + # ==================== Loss ==================== def _compute_loss(self, batch): @@ -390,11 +418,11 @@ def _compute_loss(self, batch): loss = self._model.loss(theta_latent, conditions) - if self.cfg.inference.discard_samples: + if self.discard_samples: with torch.no_grad(): self._model.eval() log_prob = self._model.log_prob(theta_latent, conditions).cpu() - discard_mask = (log_prob - theta_logprob) < self.cfg.inference.log_ratio_threshold + discard_mask = (log_prob - theta_logprob) < self.log_ratio_threshold batch.discard(discard_mask) return loss, {"loss": loss.item()} @@ -426,6 +454,7 @@ def on_epoch_end(self, epoch: int, val_metrics: Dict[str, float]) -> Optional[Di self._best_model.load_state_dict( {k: v.clone() for k, v in self._model.state_dict().items()} ) + self._sync_embedding_to_best() log({"checkpoint": epoch}) if self._scheduler is not None: @@ -472,7 +501,7 @@ def sample_posterior(self, num_samples: int, conditions=None) -> dict: return self._sample(num_samples, conditions, gamma=self._posterior_gamma) def sample_proposal(self, num_samples: int, conditions=None) -> dict: - if self._total_epochs_trained < self.loop_config.prior_epochs: + if self._total_epochs_trained < self.prior_epochs: return self.sample_prior(num_samples) result = self._sample(num_samples, conditions, gamma=self._proposal_gamma) log({ @@ -513,16 +542,19 @@ def load(self, node_dir) -> None: self._model = self._create_model(self._init_theta, self._init_conditions) self._best_model = copy.deepcopy(self._model) - opt_cfg = self.cfg.optimizer - self._optimizer = AdamW(self._model.parameters(), lr=opt_cfg.lr, betas=opt_cfg.betas) + self._optimizer = AdamW( + self._model.parameters(), + lr=self.lr, + betas=self.betas, + ) self._scheduler = ( ReduceLROnPlateau( self._optimizer, mode="min", - factor=opt_cfg.lr_decay_factor, - patience=opt_cfg.scheduler_patience, + factor=self.lr_decay_factor, + patience=self.lr_patience, ) - if opt_cfg.lr_decay_factor < 1.0 else None + if self.lr_decay_factor < 1.0 else None ) self.networks_initialized = True diff --git a/falcon/estimators/stepwise_base.py b/falcon/estimators/stepwise_base.py index aead236..de1f1d9 100644 --- a/falcon/estimators/stepwise_base.py +++ b/falcon/estimators/stepwise_base.py @@ -45,38 +45,20 @@ class StepwiseEstimator(BaseEstimator): - save/load """ - def __init__( + def setup( self, simulator_instance, - loop_config: TrainingLoopConfig, theta_key: Optional[str] = None, condition_keys: Optional[List[str]] = None, ): - """ - Initialize the stepwise estimator. - - Args: - simulator_instance: Prior/simulator instance - loop_config: Training loop configuration - theta_key: Key for theta in batch data - condition_keys: Keys for condition data in batch - """ + """Initialise runtime state shared by all stepwise estimators.""" self.simulator_instance = simulator_instance - self.loop_config = loop_config self.param_dim = simulator_instance.param_dim - self.cache_on_device = loop_config.cache_on_device - - # Key configuration for Batch access self.theta_key = theta_key self.condition_keys = condition_keys or [] - self._terminated = False self._total_epochs_trained: int = 0 - - # Networks initialized flag (managed by subclass) self.networks_initialized = False - - # History tracking self.history = { "train_ids": [], "val_ids": [], @@ -155,22 +137,16 @@ def on_epoch_end(self, epoch: int, val_metrics: Dict[str, float]) -> Optional[Di # ==================== Concrete Methods ==================== async def train(self, buffer) -> None: - """ - Main training loop with epochs and early stopping. - - Args: - buffer: BufferView providing access to training/validation data - """ - cfg = self.loop_config + """Main training loop with epochs and early stopping.""" keys = [f"{self.theta_key}.value", f"{self.theta_key}.log_prob", *[f"{k}.value" for k in self.condition_keys]] - await self._train(buffer, cfg, keys) + await self._train(buffer, keys) - async def _train(self, buffer, cfg, keys) -> None: + async def _train(self, buffer, keys) -> None: """Epoch-based training with CPU-cached dataloader.""" - sync_every = cfg.cache_sync_every if cfg.cache_sync_every > 0 else 1 + sync_every = self.cache_sync_every if self.cache_sync_every > 0 else 1 - train_cache = buffer.cached_loader(keys, max_cache_samples=cfg.max_cache_samples) + train_cache = buffer.cached_loader(keys, max_cache_samples=self.max_cache_samples) val_cache = buffer.cached_val_loader(keys, max_cache_samples=0) train_cache.sync() @@ -181,7 +157,7 @@ async def _train(self, buffer, cfg, keys) -> None: total_steps = 0 t0 = time.perf_counter() - for epoch in range(cfg.max_epochs): + for epoch in range(self.max_epochs): log({"epoch": self._total_epochs_trained + 1}) # Periodic incremental sync @@ -190,12 +166,12 @@ async def _train(self, buffer, cfg, keys) -> None: val_cache.sync() # === Training phase === - steps_per_epoch = max(1, train_cache.count // cfg.batch_size) + steps_per_epoch = max(1, train_cache.count // self.batch_size) train_metrics_sum = {} num_train_batches = 0 for step in range(steps_per_epoch): - batch = train_cache.sample_batch(cfg.batch_size) + batch = train_cache.sample_batch(self.batch_size) metrics = self.train_step(batch) for k, v in metrics.items(): @@ -215,10 +191,10 @@ async def _train(self, buffer, cfg, keys) -> None: # === Validation phase === val_metrics_sum = {} num_val_samples = 0 - val_steps = max(1, val_cache.count // cfg.batch_size) + val_steps = max(1, val_cache.count // self.batch_size) for step in range(val_steps): - batch = val_cache.sample_batch(cfg.batch_size) + batch = val_cache.sample_batch(self.batch_size) metrics = self.val_step(batch) bs = len(batch) @@ -249,7 +225,7 @@ async def _train(self, buffer, cfg, keys) -> None: train_loss = train_metrics_avg.get("loss", float("nan")) val_loss = val_metrics_avg.get("loss", float("inf")) summary = ( - f"Epoch {self._total_epochs_trained + 1}/{cfg.max_epochs}" + f"Epoch {self._total_epochs_trained + 1}/{self.max_epochs}" f" | steps={total_steps}" ) if n_sims is not None: @@ -274,7 +250,7 @@ async def _train(self, buffer, cfg, keys) -> None: ) self._total_epochs_trained += 1 - if epochs_no_improve >= cfg.early_stop_patience: + if epochs_no_improve >= self.early_stop_patience: info("Early stopping triggered.") break @@ -343,12 +319,26 @@ def __init__( "standard_normal": transform to N(0,I) via simulator.inverse/forward "hypercube": transform to hypercube via simulator.inverse/forward """ - super().__init__( - simulator_instance=simulator_instance, - loop_config=loop_config, - theta_key=theta_key, - condition_keys=condition_keys, - ) + self.max_epochs = loop_config.max_epochs + self.batch_size = loop_config.batch_size + self.early_stop_patience = loop_config.early_stop_patience + self.cache_sync_every = loop_config.cache_sync_every + self.max_cache_samples = loop_config.max_cache_samples + self.cache_on_device = loop_config.cache_on_device + self.prior_epochs = loop_config.prior_epochs + + # Runtime state normally set by StepwiseEstimator.setup() + self.simulator_instance = simulator_instance + self.param_dim = simulator_instance.param_dim + self.theta_key = theta_key + self.condition_keys = condition_keys or [] + self._terminated = False + self._total_epochs_trained: int = 0 + self.networks_initialized = False + self.history = { + "train_ids": [], "val_ids": [], "epochs": [], + "train_loss": [], "val_loss": [], "n_samples": [], "elapsed_min": [], + } self.posterior_cls = posterior_cls self.embedding_config = embedding_config @@ -357,26 +347,26 @@ def __init__( self.inference_config = inference_config self.latent_mode = latent_mode - # Device setup if device: self.device = torch.device(device) else: self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") debug(f"Auto-detected device: {self.device}") - # Model (initialized lazily) self._model: Optional[nn.Module] = None self._best_model: Optional[nn.Module] = None self._best_loss: float = float("inf") - # Stored tensors from first batch (for model rebuild on load) self._init_theta: Optional[torch.Tensor] = None self._init_conditions: Optional[Dict[str, torch.Tensor]] = None - # Optimizer/scheduler (initialized lazily) self._optimizer: Optional[AdamW] = None self._scheduler: Optional[ReduceLROnPlateau] = None + def setup(self, simulator_instance=None, theta_key=None, condition_keys=None): + """No-op: LossBasedEstimator is fully initialised in __init__ (deprecated factory pattern).""" + pass + # ==================== Model Creation ==================== def _build_model(self, batch) -> nn.Module: @@ -599,7 +589,7 @@ def sample_posterior(self, num_samples: int, conditions: Optional[Dict] = None) def sample_proposal(self, num_samples: int, conditions: Optional[Dict] = None) -> dict: """Sample from widened proposal distribution for adaptive resampling.""" - if self._total_epochs_trained < self.loop_config.prior_epochs: + if self._total_epochs_trained < self.prior_epochs: return self.sample_prior(num_samples) result = self._sample(num_samples, conditions, gamma=self.inference_config.gamma) log({ diff --git a/plans/COLAB_API_PLAN.md b/plans/COLAB_API_PLAN.md new file mode 100644 index 0000000..74b15dc --- /dev/null +++ b/plans/COLAB_API_PLAN.md @@ -0,0 +1,745 @@ +# Plan: Notebook / Colab API for Falcon + +> **Status: living plan**, tracked by issue #58. The phasing and open questions +> here are roadmap and become obsolete as work lands (the checklist of record is +> the issue). The design rationale (the config-shape taxonomy, the flat surface, +> the `_target_` rule, the Ray lifecycle, the JAX notes) is durable and will +> graduate to a permanent design doc under `docs/` once implementation is well +> underway; this file is then pruned or removed. + +## Motivation + +Falcon is currently CLI-first: the only supported entry point is `falcon launch`, +and the runtime, the blessed TUI, and signal handling are fused inside +`launch_mode` in `falcon/cli.py`. This works for batch jobs on a workstation or +cluster, but it makes the framework hard to teach and hard to explore. + +The intended end state is **pedagogical and expert-friendly at the same time**: a +set of notebooks that show off what Falcon does, where a learner can tweak a +config value or define a brand-new simulator in a cell and immediately re-run +inference, and where an ML expert finds an API consistent with the libraries +they already know (sklearn, Keras, sbi). For that, Python (not the shell) has to +be a first-class front door. The CLI should become one frontend over a clean +core, not the core itself. + +This plan is the design for that API. It is independent of the end-of-run +summary work (PR #56). + +## Status: decisions taken + +The following were settled during design discussion and are treated as decided +in the rest of this document: + +- **Flat config surface, committed.** Config is set through flat, prefixed, + typed keyword arguments (`loop_num_epochs=600`), not nested dicts or nested + config objects. The YAML file stays nested; a deterministic transform bridges + the two. +- **`launch()` blocks by default.** A non-blocking `launch(wait=False)` mode is a + supported, real escape hatch, justified by the live-monitoring use case. +- **Ray cluster setup is separate from running a graph.** `falcon.init()` sets up + or connects to Ray; `launch()` reuses it. +- **The prior list-syntax is kept** as `Product`'s own field encoding. It is not + desugared into `_target_` blocks, and the existing list-of-lists syntax also + serves as the Python API — no separate typed-marginal objects needed for v1. +- **Worked notebooks are the onboarding vehicle.** The per-run auto-saved + `config.yml` is the bridge to the CLI. +- **The v1 notebook display is one interleaved, color-tagged log stream** (driver + plus all node logs). Structured ipywidgets displays are optional phase 2. +- **`falcon.Simulator` base class is not needed for v1.** Duck typing is + sufficient; a base class adds value only when actor-environment hooks (JAX + passthrough) are implemented. +- **`falcon.session()` context manager is deferred.** Not needed before the basic + API works; add later for CI scoped lifetimes. + +Still open (see Open Questions): the outcome of the cloudpickle spike (gates +everything notebook-class-related) and the exact flattened-signature parameter +counts. + +## Design principles + +1. **Outside-in.** The API is whatever makes the target notebook cells (below) + read cleanly. We design the cells first and the surface second. +2. **The CLI and the API are siblings, not a hierarchy.** Both are thin wrappers + over one pure pipeline function. Neither imports the other's concerns (no TUI + in the API, no `Run`-returning in the CLI). +3. **The surface style of a config slot is derived from its type, not chosen by + taste.** See the config-shape taxonomy below. This is what keeps a partly-flat, + partly-object API from looking arbitrary. +4. **No `**kwargs` in any public constructor.** Jedi (Colab/Jupyter completion) + cannot introspect `**kwargs`; a single `**kwargs` destroys autocomplete and + the YAML-to-API mapping for that call. Every public signature is explicit. +5. **No hidden magic.** No environment auto-detection, no notebook-vs-terminal + heuristics, no silently writing user cell code to temp modules. +6. **Borrow, do not invent.** This is a solved problem (Hydra, Pydantic, spaCy). + See Standard Precedents. The only genuinely new piece is the notebook-class + escape hatch. +7. **Everything is inspectable.** Configs, graphs, and runs all get rich notebook + reprs. +8. **The notebooks are the spec and the test.** Example notebooks are executed in + CI; if the API drifts, the notebooks break. + +## Audience: experts and students, one design + +The API serves ML experts and students with a single surface, not two. + +- For **experts**, the flat typed kwargs, the sum-as-object pattern, the + autocomplete-first design, and the YAML round-tripping are idiom-consistent + with sklearn, Keras, and HuggingFace, and serve real needs (hyperparameter + tuning, reproducible config, sweeps). +- For **students**, the same surface is reached through worked example + notebooks. A beginner starts by mutating a working notebook, never by + constructing config from a blank cell. The build-from-scratch surface is a + later lesson. + +There is no separate beginner API to maintain. The expert-optimal design is +gentle enough to be a teaching destination, provided the on-ramp is +example-driven. The example notebooks carry the beginners; the API carries the +experts. + +## The two registers + +| Register | Teaching mode | Entry | Backing object | +|----------|---------------|-------|----------------| +| **Config** | "Change a knob, re-run" | `falcon.config("config.yml")`, `.override(...)` | `Config` (wraps `DictConfig`) | +| **Programmatic** | "Build your own model" | `falcon.Graph()`, `.add_node(...)` | `Graph` | + +Both lower to the same runtime `Graph`. YAML stays valid forever; the +programmatic path is additive. The two are mixable: a config can embed a live +Python class as a node's `simulator`, and a programmatically built `Graph` can be +launched with flat run-level overrides. + +## The config-shape taxonomy + +Every config slot has one of four shapes. The shape determines the API surface. +This is the single rule that makes the whole design coherent. + +| Shape | Definition | Example | API surface | YAML form | +|-------|------------|---------|-------------|-----------| +| **Product** | Fixed set of fields, all always apply | `loop`, `optimizer`, `buffer` | Flat prefixed kwargs: `loop_num_epochs=600` | nested block / dotted keys | +| **Sum** | Pick one of N; valid fields depend on the choice | flow architecture, `estimator`, `simulator` | A typed object; the class is the choice: `network=Flow.MAF(...)` | tagged block: `{_target_: maf, ...}` | +| **Composite** | Recursive; structure is data, depth unbounded | embedding pipeline | A construction expression: `Sequential(...)` | recursive `_target_`/`_input_` | +| **Collection** | List of N homogeneous components | `priors` | A list of typed elements | a YAML list | + +Why each is what it is: + +- A **product** has a fixed, known field list, so it flattens into named kwargs. + The names never change. Autocomplete shows one honest popup. +- A **sum** cannot be flattened: a flat `network_num_bins` is meaningless when the + user picked MAF. So a sum stays an object, and the object's class is the choice. + `Flow.MAF(...)` exposes exactly MAF's fields with autocomplete; `Flow.NSF(...)` + exposes NSF's. Picking the constructor picks the valid field set. +- A **composite** cannot be flattened either, and is not even a fixed sum: an + embedding `_input_` can itself be an embedding, or a list of them, to arbitrary + depth. The surface is a construction expression, the same idiom as + `torch.nn.Sequential`. +- A **collection** is a list, so its surface is a list. Each element is itself + typed (and may itself be a product or sum). + +**Flattening stops at sum, composite, and collection boundaries.** Products +flatten only within their owning scope. Crossing into a chosen-class object +restarts flattening inside that object. Consequence: `estimator_loop_num_epochs` +never exists. `estimator` is a sum slot (Gaussian, Flow, or none), so it is an +object, and the `loop_*` flattening happens inside the chosen estimator: +`estimator=Gaussian(loop_num_epochs=600)`. + +The shape is **per-slot and per-estimator**, not global. Worked example: the +`network` slot is a product in `Gaussian` (a fixed MLP-shaped posterior network, +flat `network_hidden_dim=...`) but a sum in `Flow` (13 architectures, object +`network=Flow.MAF(...)`). Same field name, different shape, because the +underlying type differs. The surface follows the type. + +## The flat config surface and the YAML bridge + +### Flat Python, nested YAML + +The Python API shape and the `config.yml` shape are connected by a mapping, not +by identity. The YAML stays nested (readable, editable, organizationally +grouped); the Python surface is flat (one autocomplete popup, all defaults +visible). They are bridged by a deterministic prefix transform: + +``` +Python kwarg YAML key +loop_num_epochs <-> loop.num_epochs +optimizer_lr <-> optimizer.lr +network_hidden_dim <-> network.hidden_dim +``` + +The transform splits off only the first segment, and only when it is a known +section prefix. Field names may themselves contain underscores (`hidden_dim`) +with no ambiguity. The only constraint: section names contain no underscore. + +### Implementation: synthesized signatures, no `**kwargs` + +A flat `Gaussian(**kwargs)` autocompletes to nothing, so that is forbidden. But +the ~25 flat parameters must not be hand-maintained either. Instead: + +- The nested `@dataclass` config classes (`GaussianConfig` with fields `loop`, + `network`, `optimizer`, `inference`, etc.) remain the **single source of + truth**: they are the YAML schema, the OmegaConf structured-config validation + schema, and the basis for the flat signature. +- The flat `__init__` signature is **synthesized** from them: walk the nested + fields, build an `inspect.Signature` of `loop_*` / `network_*` / etc. + parameters with their annotations and defaults, and assign it to + `Estimator.__init__.__signature__`. IPython and Jedi honor an explicit + `__signature__`, so Colab's popup shows the full flat list with defaults even + though the code does not literally spell them out. +- The constructor body expands `prefix_field` back into the nested `Config`. + +One generator serves every estimator. Zero signature duplication. + +### Defaults visibility + +Scalar defaults show inline in the Colab signature popup. `field(default_factory=...)` +defaults show only as ``. Therefore: + +- Prefer immutable scalar/tuple defaults over `default_factory`. For example + `betas: tuple[float, float] = (0.9, 0.9)` shows in the popup; a + `field(default_factory=lambda: [0.9, 0.9])` does not. +- `?` shows the docstring + signature; `??` shows the source (always the full + truth); constructing the object with no args and reading its dataclass repr + resolves every default including factory ones. +- Dataclass per-field docstrings do not surface in the popup, so each config + class docstring enumerates its fields with units and semantics. + +### Flat kwargs vs dotted overrides (do not conflate) + +There are two distinct mechanisms: + +- **Flat typed kwargs** are the *constructor surface* of a fixed-schema object + (an estimator, the buffer). They autocomplete. Used in `Gaussian(loop_num_epochs=...)`. +- **Dotted-string overrides** are the *arbitrary-deep-path* escape hatch, used to + override into a loaded `Config` whose paths include arbitrary user-chosen node + names: `cfg.override("graph.theta.estimator.loop.num_epochs=150")`. These do + not autocomplete; they cannot, because node names are data. + +Flat kwargs are the discovery path; dotted overrides are the catch-all. They are +not interchangeable and the docs must keep them distinct. + +## Step 0: unify `_target_` resolution — DEFERRED + +**Decision (2026-06-08): Step 0 is deferred indefinitely.** + +The original motivation was to replace `net_type` (a bare string discriminator) +with `Flow.MAF()` / `Flow.NSF()` variant classes so that per-variant +hyperparameters could be exposed with full autocomplete. That surface only becomes +load-bearing when those hyperparameters are actually exposed. Right now all 13 +flow builders are called identically — `builder(theta, s, z_score_x=None, +z_score_y=None)` — so `net_type` is just a plain product field, and the +variant-class refactor is pure churn with no functional benefit. + +If per-variant hyperparameters are ever needed before a full variant-class refactor +is worthwhile, the pragmatic escape hatch is `net_config: dict = {}` passed +through to the builder. + +Similarly, `NetworkConfig` conflates architecture (`net_type`) and normalization +fields (`theta_norm`, `norm_momentum`, etc.), but untangling them has no practical +benefit until variant-specific params are added. + +The prior list-syntax (`['uniform', -100, 100]`) is also left as-is: it is already +the Python API, the same list-of-lists form works in both YAML and notebook code, +and no typed-marginal object layer is needed. + +**Consequence for sequencing**: implementation starts at Step 1. + +## Target notebook UX (the spec) + +### Cell story A: tweak a config (e.g. `examples/01_minimal` as a notebook) + +```python +import falcon + +cfg = falcon.config("config.yml") # Config object, rich repr renders the YAML +cfg + +cfg = cfg.override( # dotted strings for arbitrary deep paths + "buffer.min_samples=2000", + "graph.theta.estimator.loop.num_epochs=150", +) + +run = falcon.launch(cfg) # blocks; live progress in the cell +run # rich repr: status, runtime, final losses, log paths + +run.plot_metrics() +samples = run.sample_posterior(n=10_000) +falcon.corner(samples) +``` + +### Cell story B: define a new model (the "build your own" lesson) + +```python +import falcon, torch + +class MySimulator: # plain callable, duck typing + def __call__(self, theta): + return theta + 0.1 * torch.randn_like(theta) + +graph = falcon.Graph() + +graph.add_node( + "theta", + simulator=falcon.priors.Product([ # collection -> list of typed marginals + ['uniform', -5, 5], + ['uniform', -5, 5], + ]), + estimator=falcon.estimators.Gaussian( # sum -> object; products inside flatten + loop_num_epochs=300, + optimizer_lr=1e-3, + inference_gamma=0.5, + ), + evidence=["x"], +) + +graph.add_node( + "x", + simulator=MySimulator(), # __main__ class, shipped via cloudpickle + parents=["theta"], + observed=obs_array, # ndarray accepted directly + ray_num_gpus=0.5, # node-level product -> flat +) + +graph # rich repr: Mermaid DAG + +run = falcon.launch(graph) +``` + +### A Flow estimator with a composite embedding + +```python +estimator=falcon.estimators.Flow( + loop_num_epochs=600, # product -> flat + optimizer_lr=1e-3, # product -> flat + network_net_type="maf", # product -> flat string field + embedding={ # composite -> nested _input_ config + '_target_': 'MyCNN', + 'channels': 32, + '_input_': { + '_target_': 'falcon.embeddings.PCAProjector', + 'n_components': 64, + '_input_': 'x', + }, + }, +) +``` + +These cells are the acceptance test: if a notebook needs an awkward cell, the API +is wrong. + +## Architecture + +### Step 1: extract the pure pipeline (no behavior change) + +Split `launch_mode` in `falcon/cli.py` into three: + +- `_run_pipeline(cfg, *, auto_sample, timeout, stop_check, log_sink) -> Path`: + graph build, deploy, train, optional posterior sampling, end-of-run summary, + teardown. No terminal control, no signal handlers, no Ray init/shutdown (see + Ray lifecycle). +- `launch_mode(cfg, interactive, ...)`: CLI wrapper. Builds the TUI or the + `_GracefulShutdown` handler, wires `stop_check`, owns Ray init/shutdown for the + one-shot process, calls `_run_pipeline`. +- `falcon.launch(...)`: API wrapper (below). + +`stop_check` and `log_sink` are injected, so the CLI passes TUI-aware versions +and the API passes notebook-aware ones. This refactor is worth doing on its own +merits (testability, separation of concerns) and ships with CLI behavior +byte-for-byte unchanged. + +### The CLI conforms to the API + +The test of the structure: `falcon launch` and `falcon.launch()` should read as +two thin adapters over one core, differing only in frontend (terminal TUI vs cell +output) and input format (argv vs Python objects). CLI flags map 1:1 onto API +parameters (`-o` to `output=`, `key=value` to `overrides=`, +`--no-auto-sample` to `auto_sample=`, `--timeout` to `timeout=`). If +the CLI ends up with pipeline logic the API path does not also exercise, the +split has leaked. + +## The public API + +``` +falcon.init(**ray_init_kwargs) +falcon.config(source) -> Config # source: path | dict | DictConfig +falcon.launch(target, output=None, *, overrides=None, + auto_sample=True, timeout=None, wait=True) -> Run | LaunchHandle +falcon.shutdown() +``` + +- **`Config`** wraps `DictConfig`: dict-like access, `.override(*dotted_strings)`, + `.to_yaml()`, `_repr_markdown_`. OmegaConf does the real work. +- **`falcon.launch(target, ...)`** accepts a `Config` / dict / path **or** a + `Graph`. Buffer, network, and other model config belongs in the config object + (or via `overrides=`), not as kwargs on `launch()`. `output`, `timeout`, + `auto_sample`, and `wait` are the only run-level options here. Cluster-level Ray + config lives on `falcon.init()`. `launch()` calls `_run_pipeline` with no TUI + and a notebook log sink. +- **`Run`** is returned (blocking mode). It gains methods, not top-level + functions: `run.sample_posterior(n)`, `.sample_prior(n)`, `.sample_proposal(n)` + (each writes NPZ for CLI parity and returns the samples), `run.plot_metrics()`, + `run.status`, `run.runtime`, `run.config`. `load_run` is reused as-is. There is + no `falcon.load` alias and no top-level `falcon.sample()`; sampling is a `Run` + method because a `Run` owns the config and the trained graph. +- **`falcon.init(**ray_init_kwargs)`** is a thin wrapper around `ray.init()`. + Named `num_cpus` / `num_gpus` parameters are omitted: when connecting to an + existing cluster (`address=...`) they are meaningless; when starting a local + cluster Ray detects resources automatically. Pass any `ray.init()` kwarg + directly. Idempotent: a second call is a no-op. + +### Blocking vs non-blocking + +`launch()` **blocks by default** and returns a finished `Run`. This matches every +mainstream ML library (`model.fit()` in Keras, Lightning, `transformers.Trainer`, +sbi), gives the simplest mental model, and avoids a half-trained-`Run` +concurrency surface. The kernel-busy cost is mitigated by live in-cell progress +(see Live Monitoring) and a graceful interrupt: a notebook kernel-interrupt maps +to the existing graceful-stop machinery and returns a partial `Run`, not a +traceback. + +`launch(wait=False)` is a **supported** non-blocking mode: training runs in a +background thread and `launch` returns a `LaunchHandle` with `.wait()`, +`.status`, `.stop()`, and the live-updating display. It exists because the +live-interactive-monitoring use case genuinely needs a free kernel. It is opt-in, +never the default, so the simple mental model stays intact for everyone who does +not need it. + +### Programmatic graph builder + +`Graph` and `Node` are already plain classes. Add `Graph.add_node`: + +``` +graph.add_node(name, simulator=..., estimator=None, parents=None, + evidence=None, observed=None, ray_num_gpus=..., ray_num_cpus=..., ...) +``` + +- `simulator=` and `estimator=` are object slots (sums). `estimator` is optional; + omitting it means the node is not inferred. +- `observed=` accepts an ndarray/tensor directly (the YAML path's + `"file.npz['y']"` string is not forced on notebook users). +- `ray_num_gpus` etc. are node-level product config and flatten. +- `add_node` validates incrementally with notebook-friendly errors ("node 'x' + lists parent 'theat', not defined; did you mean 'theta'?"). + +`falcon.Simulator` is a documented, optional base class so the "define your own" +lesson has an obvious starting point. Duck typing still works; the base class +anchors the docs and is where the actor-environment hooks live (see JAX). + +## Ray lifecycle + +Provisioning a real multi-node cluster is never `launch()`'s job; that happens +before any Falcon code runs, via Ray's own tooling, and Falcon only connects. +Starting a local Ray is cheap and a beginner should not have to think about it. +Either way, Ray is initialized **once per session** and reused. + +``` +falcon.init(**ray_init_kwargs) # optional, once, idempotent; thin wrapper around ray.init() +falcon.launch(...) # uses existing Ray; lazily calls init() if none; never shuts down +falcon.shutdown() # explicit teardown +``` + +- `falcon.init()` connects to an existing cluster (`address=...` passed as a + kwarg) or starts a local one. Idempotent: a second call is a no-op. + **Cluster-level Ray resources live here**, not on `launch()`. +- `launch()` reuses an existing Ray, lazily calls `init()` with defaults if none + exists (so a beginner does nothing), and **never shuts Ray down on return**, so + state and actors survive across cells. +- The CLI keeps init-and-shutdown inside its one-shot process. This is a + deliberate, documented divergence from the API path. +- `falcon.session()` context manager is deferred; use `falcon.init()` + + `falcon.shutdown()` explicitly for now. + +Beginner: do nothing, the first `launch()` brings up local Ray. Expert on a +cluster: `falcon.init(address=...)` once at the top, then many `launch()` calls +reuse it. + +Note: per-node `ray_num_gpus` on `add_node` is node *placement*, a node property, +unrelated to cluster setup. + +## Notebook-defined models: cloudpickle and the escape hatch + +A class defined in a notebook lives in `__main__` and has no importable path, so +the `_target_` string mechanism cannot find it. The plan: notebook users pass the +class or instance object itself into `add_node`, and Ray ships it to actors via +cloudpickle. + +### What cloudpickle does, and what it does not serialize + +- A `__main__` class is serialized **by value** (its code and method bytecode). +- Modules that its methods *reference* are serialized **by reference**: if a + method calls `torch.randn(...)` and `torch` was imported at the top of the + notebook, cloudpickle records "the module named `torch`" and the Ray worker + re-runs `import torch` on unpickle. The import statement does not travel; a + re-import on the worker does. +- Therefore top-of-notebook imports work as-is. Moving `import torch` into + `__init__` changes nothing for correctness. +- The real requirement is **environment parity**: the Ray worker must be able to + import the referenced packages. For a local Ray started by the notebook the + worker is the same environment, so this is automatic. For a remote cluster the + packages must exist there (or be supplied via `runtime_env`). +- Cloudpickle captures, transitively, the global names the methods reference. A + notebook class referencing another notebook-defined helper class drags the + helper in by value too. + +### Spike, gating the rest of the plan + +This is the load-bearing assumption of the pedagogical story and **must be +validated in a spike before the API is committed**. Risks to test: + +- Closures over large notebook globals bloating the pickle. +- Transitive capture of other notebook-defined classes. +- Torch modules / CUDA tensors as constructor args. +- Re-running the defining cell mid-session (class identity changes; a new + `launch()` picks up the new class, which is the desired edit-rerun behavior, but + an in-flight run keeps the old one). + +If cloudpickle proves unreliable, the fallback is an explicit +`falcon.register(MyClass)` that snapshots source into a synthetic importable +module. We do not silently write temp modules. + +### The escape hatch: serialization round-trip + +Every standard config system (Hydra, spaCy, AllenNLP) assumes components are +importable. Notebook `__main__` classes break that: they have no import path, so +they cannot serialize back to a `_target_` string. This is handled with a +deliberate, narrow exception: + +- When `launch()` saves the resolved `config.yml`, a live notebook-defined object + is written as a placeholder, `""`, not a real `_target_`. +- The saved `config.yml` stays valid and readable but the run is flagged **not + reproducible from YAML alone**. +- The object still ships to Ray fine, because that path is cloudpickle, not the + import path. + +So a run using only library components produces a fully runnable `config.yml`; a +run using notebook-defined classes produces a `config.yml` that is viewable and +instructive but not replayable without the notebook. The example notebooks must +state this honestly. Source extraction to a `_live_objects.py` artefact is not +implemented for v1 — the notebook itself is already the natural reproducibility +artefact. + +## JAX and process-global state + +JAX simulators and embeddings need extra care because JAX has process-global +state that does **not** serialize. Ray actors are separate processes, so: + +- **`jax_enable_x64` does not travel.** Setting it in a notebook cell affects + only the driver. Each actor starts in 32-bit and must re-establish x64 itself, + before its first JAX array is created, either in the simulator's `__init__` or + via a per-actor environment variable (`JAX_ENABLE_X64=1`). This is the one + legitimate case where `__init__`-time setup is required (it is not about + imports). +- **GPU memory preallocation.** JAX preallocates a large GPU fraction on first + use, per process. Several actors on one GPU collide. Set `XLA_PYTHON_CLIENT_PREALLOCATE=false` + (or a memory fraction) per actor. +- **Do not ship `jit`-compiled artifacts.** Store the plain function and `jit` it + inside `__init__`; each actor compiles its own. Compilation is per-process + anyway. +- **PRNG keys.** A key pickles fine, but a single key copied to every actor makes + every actor produce identical "random" simulations. Each actor must fold in + something unique (actor index, node name). +- **JAX arrays as constructor args** are device-committed and risky across the + serialization boundary; pass plain numpy and convert inside `__init__`. + +Design consequence: the node's Ray actor config should expose a first-class +`env` / `runtime_env` passthrough so users can set `JAX_ENABLE_X64`, +`XLA_PYTHON_CLIENT_PREALLOCATE`, etc. per actor without hand-rolling it. The +`add_node` docs should show the JAX pattern explicitly (x64 and `jit` in +`__init__`, per-actor key splitting). + +## Live monitoring and dynamic output + +### Do not port the blessed TUI + +The CLI TUI is terminal mechanism: alternate-screen mode, escape codes, a fixed +footer carved from a scrolling region, `cbreak` keyboard capture. None of it +exists in a notebook cell. Mirror the *information*, not the mechanism. + +### One data source, three frontends + +The blessed TUI, `falcon monitor`, and the notebook display are all frontends +over one source, `MonitorBridge.get_status()` (per-node epoch/loss/sims, buffer +stats). The notebook display is a third renderer of the same status dict, not a +reimplementation. + +``` +MonitorBridge.get_status() + +-- blessed TUI (falcon launch, terminal) + +-- falcon monitor (separate TUI process) + +-- notebook display (new) +``` + +### v1: one interleaved, color-tagged log stream + +The v1 notebook display is deliberately simple: dump **every** log source into +the single cell stream, interleaved. That is the driver's `output.log` plus every +node's `output.log`. No widgets, no status panel, no polling of `MonitorBridge`. + +- The mechanism mostly exists: Ray already forwards actor stdout to the driver + (`log_to_driver`, which the CLI sets from `console.level`), and the driver's own + log is already on stdout. The notebook log sink just prints what arrives. +- Each line is tagged by its source for scanability: a stable per-node color (hash + the node name into a small palette) and/or emoji, with the driver getting its + own. ANSI color codes and emoji both render in Jupyter/Colab cell output. Line + shape: `{tag} {node-name} HH:MM:SS message`. +- Interleaving across nodes is accepted, by design. The point is liveness: the + cell visibly does something. A scannable color tag makes the mixed stream + readable enough. + +Honest caveat: Ray batches the forwarded actor output, so the interleaving is +approximately time-ordered, not exact. Fine for a liveness signal; the docs +should not promise precise ordering. + +This is the v1 display. It always works, needs no extra dependency, and is enough +for the blocking `launch()` path. + +### Phase 2: structured displays (optional, later) + +Richer renderings are a later, optional add-on, not v1: + +- **ipywidgets dashboard**: a fixed `VBox` of one compact status row per node + (status, epoch progress bar, loss) plus an `Output` widget for the log stream. + Polls `MonitorBridge.get_status()`. Reproduces the TUI's panel-plus-scroll + split. Needs the `notebooks` extra. Per-node *status rows* shown all at once + (scales to dozens of nodes); per-node *log tails* are not, since the v1 stream + already carries them interleaved. Full per-node log inspection stays the job of + `falcon monitor`. +- **`display(..., display_id=True)` + `.update()`**: a lighter middle option, one + updating HTML status table, IPython-only. + +These are sugar over the same information. v1 (the interleaved stream) ships +first; phase 2 is built only if the plain stream proves insufficient in practice. + +### The blocking constraint, and the routes around it + +While `launch()` blocks the kernel, the display is **push-only**: the launch loop +pushes updates out, but interactive widget callbacks (a dropdown to select +metrics or a node) cannot fire, because the kernel is busy. This is the default +outcome, not a hard law. Three routes give genuine live interactivity: + +1. **Non-blocking mode (`launch(wait=False)`).** Training runs in a background + thread, the kernel is free, ipywidgets callbacks fire normally; the user can + select metrics and filter nodes live. The half-trained-`Run` concurrency worry + does not apply, because monitoring is read-only. +2. **A separate monitor client (lowest risk).** Training runs on Ray actors, + decoupled from the driver, and `MonitorBridge` persists in the cluster. A + second kernel or notebook (or `falcon monitor`) connects to the same bridge + and is fully interactive because it is a different, non-blocked process. The + blocking `launch()` cell stays simple; interactivity lives in the companion. +3. **A cooperative-async launch loop.** If the launch loop is `async` and yields + to the kernel's asyncio event loop periodically, widget callbacks can fire + during the "blocking" cell. Legitimate but more implementation work; a + fallback, not the primary path. + +Interactive metric *exploration after* a run needs none of this: the run is done, +the kernel is free, so `run.plot_metrics(...)` with metric-picker widgets is +interactive out of the box. Only live-during-training selection needs routes 1-3. + +Recommended: keep block-by-default with the push-only display for the simple +path; offer `wait=False` for users who want a live interactive dashboard; point +at the separate monitor client (route 2) for the richest experience at the +lowest risk. + +## Rich display + +- `Config._repr_markdown_`: pretty, foldable YAML. +- `Graph._repr_html_` / `_repr_mimebundle_`: render the DAG as **Mermaid** + (Jupyter and GitHub render it natively); reuse the topology logic behind + `render_git_graph_simple`. `falcon graph` keeps ASCII for the terminal. +- `Run._repr_html_`: status, runtime, Ray size, per-node final loss, log-file + links; a compact sibling of the PR #56 end-of-run summary. +- `run.plot_metrics()`: matplotlib loss curves from `read_run`. +- `falcon.corner(samples)`: convenience posterior corner plot (optional + dependency, graceful fallback like `wandb`). + +## Standard precedents + +This is a solved problem; borrow rather than invent. + +- **Hydra / OmegaConf** (already a Falcon dependency): `instantiate()` with + `_target_` plus kwargs is exactly the Step 0 convention. Target Hydra's + `instantiate` semantics for the resolver. +- **Pydantic**: typed sectioned configs, discriminated unions, custom types that + own their own encodings. The two-layer `_target_` rule is the Pydantic + discriminated-union pattern. +- **spaCy / Thinc config + `catalogue`**: the most polished example of a + registry-based, typed, nested config designed for hand-editing. Study its + design; do not add it as a dependency (it would compete with OmegaConf/Hydra). +- **Keras** (`class_name` + `config`), **AllenNLP** (`Registrable` / `from_params`), + **Kubernetes** (`kind:` discriminator): the same pattern in other domains. +- **PyTorch** deliberately does the opposite (architecture lives in code, only + weights persist). That is the philosophy Falcon is choosing against; worth + knowing as the alternative. + +The only genuinely new piece is the notebook-class escape hatch; everything else +is on well-paved road. + +## Example notebooks (deliverables) + +Each `examples/0X_*` gets a companion `notebook.py` (jupytext percent format), +kept as source of truth alongside the existing CLI scripts. The `.ipynb` files +are generated build artefacts, not checked in. Existing `run.py` / `run_example.py` +files are left untouched; the notebook scripts are new, separate files. + +- `01_minimal`: cell story A: load config, override, launch, inspect, sample. +- `02_bimodal`: config register: compare training strategies by editing config. +- `03_composite`: programmatic register: multi-node graph, composite embedding. +- `04_gaussian`: define-your-own: write a plain callable simulator in a cell. +- `05_linear_regression`: the full define-your-own story with an explicit FFT + embedding network defined in a cell, the cloudpickle caveats surfaced, and a + check against the analytic Gaussian posterior. +- A new `examples/00_tour.ipynb`: narrative tour of the whole framework. + +These are the acceptance tests for the API. Onboarding flows through them: a +beginner starts by mutating a working notebook, and the per-run auto-saved +`config.yml` (the fully resolved config, which doubles as an all-defaults +overview) is how they later discover the file the CLI consumes. One explicit +lesson connects the saved file to `falcon launch`. + +## CI + +Execute every example notebook in CI with `pytest --nbmake` (or +`jupyter nbconvert --execute`) on a tiny config (few epochs, small buffer) so +notebook rot is caught. Add a `notebooks` extra to `pyproject.toml` +(`ipywidgets`, `matplotlib`, optionally `corner`). + +## Phasing / sequencing + +0. **Step 0: deferred.** See above. +1. **`_run_pipeline` extraction.** CLI behavior byte-for-byte unchanged; unit + tests exercise `_run_pipeline` directly. No new public API. +2. **Cloudpickle spike.** Prove or disprove notebook-`__main__` classes surviving + to Ray actors. Gate the rest of the plan on this. +3. **Flat config surface.** Synthesized signatures from the nested dataclasses, + the prefix-transform bridge, the `Config` object. Typed configs, no `**kwargs`. +4. **`falcon.init` / `launch` / `shutdown` + the v1 interleaved color-tagged log + stream.** `launch()` returns a `Run`, blocks. The CLI is refactored to conform + to the API. The config register, end to end. `01_minimal` notebook. +5. **`Graph.add_node` builder + live-object support + the escape-hatch + serialization + the JAX actor-env passthrough.** The programmatic register. + `03` / `04` / `05` notebooks. (`falcon.Simulator` base class deferred.) +6. **Rich reprs + Mermaid graph + `plot_metrics` / `corner`.** +7. **Phase-2 monitoring (optional): the ipywidgets dashboard over `MonitorBridge`, + `launch(wait=False)` + `LaunchHandle`, the separate monitor client.** Built + only if the v1 stream proves insufficient. +8. **Notebook CI + `00_tour.ipynb`.** + +## Explicitly out of scope (v1) + +- Environment auto-detection (notebook vs terminal). The CLI is for terminals, + the API for everything else; no `isatty` heuristics. This also dissolves the + original "Colab garbage output" bug: notebook users call `falcon.launch()`, + never `!falcon launch`. +- Silently writing user cell code to temp modules. +- A notebook-native config *editor* widget. Editing is done in code cells. +- A second, beginner-only API. There is one surface, reached by experts directly + and by students through worked examples. + +## Open questions + +- **Cloudpickle spike outcome.** Gates everything notebook-class-related. If it + fails, the `falcon.register` fallback applies. +- **Flattened-signature parameter counts.** The flat surface is comfortable at + sklearn scale (~20-30 params per estimator) and degrades past that. Count the + real totals during Step 3 (Flow's `InferenceConfig` alone is ~12 fields). If an + estimator's flattened signature is much larger than ~30, revisit before + implementing. +- **Where notebook runs write.** Default `output/` as today; the + rich `Run` repr surfaces the path so it is never lost. diff --git a/plans/spikes/cloudpickle_spike.py b/plans/spikes/cloudpickle_spike.py new file mode 100644 index 0000000..8984bef --- /dev/null +++ b/plans/spikes/cloudpickle_spike.py @@ -0,0 +1,335 @@ +"""Cloudpickle spike for the Falcon notebook API (issue #58, Step 2). + +Tests whether __main__-defined simulator and embedding classes survive the +Ray actor serialization boundary, covering the scenarios listed in the plan. + +Run from the repo root: + python plans/spikes/cloudpickle_spike.py + +Each test prints PASS / FAIL with a brief explanation. + +## Findings (2026-06-08) + +All scenarios pass except CUDA tensors stored as instance attributes (expected). + +PASS Basic callable, numpy return +PASS Torch simulator using torch.randn_like +PASS torch.nn.Module subclass (SmallMLP with Linear layer) +PASS Transitive __main__ dependency (class A uses class B from __main__) +PASS Closure over a small numpy array +PASS Closure over a large numpy array (~8 MB) — pickle is 8 MB; documented footgun +PASS Class redefinition (re-run cell): new class replaces old one correctly +PASS CUDA tensor constructor arg — fails as expected; numpy workaround passes + +Conclusion: cloudpickle + Ray handles all normal notebook simulator patterns. +The one constraint: do not store CUDA tensors as instance attributes; store +numpy arrays and call .cuda() inside forward()/__call__(). +Large global closures work but silently bloat the pickle on every call. +""" + +import sys +import traceback +import numpy as np +import torch +import cloudpickle +import ray + +# --------------------------------------------------------------------------- +# Ray actor that accepts a cloudpickled callable and exercises it +# --------------------------------------------------------------------------- + +@ray.remote +class WorkerActor: + """Simulates a NodeWrapper actor receiving a user-defined simulator.""" + + def run_callable(self, obj_bytes, *args): + """Deserialize obj_bytes, instantiate if it's a class, call with args.""" + obj = cloudpickle.loads(obj_bytes) + if isinstance(obj, type): + instance = obj() + else: + instance = obj + result = instance(*args) + return result + + def run_module_forward(self, obj_bytes, x_bytes): + """Deserialize a nn.Module and run a forward pass.""" + module = cloudpickle.loads(obj_bytes) + x = cloudpickle.loads(x_bytes) + with torch.no_grad(): + return cloudpickle.dumps(module(x)) + + def check_identity(self, obj_bytes): + """Return the class name of the deserialized object (instance or class).""" + obj = cloudpickle.loads(obj_bytes) + if isinstance(obj, type): + return obj.__name__ + return type(obj).__name__ + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _pack(obj): + return cloudpickle.dumps(obj) + +def _pack_tensor(t): + return cloudpickle.dumps(t) + +def run_test(name, fn): + try: + fn() + print(f" PASS {name}") + return True + except Exception as e: + print(f" FAIL {name}") + print(f" {type(e).__name__}: {e}") + if "--verbose" in sys.argv: + traceback.print_exc() + return False + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +def test_basic_callable(actor): + """Plain __main__ callable class shipped to a Ray actor.""" + + class MySimulator: + def __call__(self, theta): + return theta * 2.0 + + theta = torch.tensor([1.0, 2.0, 3.0]) + result = ray.get(actor.run_callable.remote(_pack(MySimulator), _pack_tensor(theta))) + result = cloudpickle.loads(result) if isinstance(result, bytes) else result + + # run_callable returns whatever the simulator returns; re-pack for transfer + # Actually the actor returns the raw result. Let's just get it and compare. + assert isinstance(result, torch.Tensor) or result is not None + + +def test_basic_callable_v2(actor): + """Verify the actor can deserialize and the result is correct.""" + + class DoubleSimulator: + def __call__(self, theta): + import torch as _torch + return _torch.tensor([x * 2.0 for x in theta.tolist()]) + + theta = torch.tensor([1.0, 2.0, 3.0]) + # Pack theta as plain numpy so it survives without torch serialization issues + theta_np = theta.numpy() + result = ray.get(actor.run_callable.remote(_pack(DoubleSimulator), theta_np)) + assert isinstance(result, (np.ndarray, torch.Tensor)) + + +def test_torch_simulator(actor): + """Simulator that uses torch inside __call__.""" + + class TorchSimulator: + def __call__(self, theta): + import torch as _torch + return theta + 0.1 * _torch.randn_like(theta) + + theta = torch.zeros(5) + result = ray.get(actor.run_callable.remote(_pack(TorchSimulator), theta)) + assert result is not None + + +def test_torch_nn_module(actor): + """nn.Module subclass defined in __main__.""" + + class SmallMLP(torch.nn.Module): + def __init__(self): + super().__init__() + self.fc = torch.nn.Linear(4, 2) + + def forward(self, x): + return self.fc(x) + + model = SmallMLP() + x = torch.randn(3, 4) + out_bytes = ray.get(actor.run_module_forward.remote(_pack(model), _pack_tensor(x))) + out = cloudpickle.loads(out_bytes) + assert out.shape == (3, 2) + + +def test_transitive_dep(actor): + """Class A uses helper class B, both defined in __main__.""" + + class Preprocessor: + def __call__(self, x): + import torch as _torch + return x - _torch.mean(x) + + class SimulatorWithHelper: + def __call__(self, theta): + prep = Preprocessor() + return prep(theta) + + theta = torch.tensor([1.0, 2.0, 3.0]) + result = ray.get(actor.run_callable.remote(_pack(SimulatorWithHelper), theta)) + assert result is not None + + +def test_closure_over_array(actor): + """Class closes over a numpy array (fixed dataset) defined in the outer scope.""" + fixed_data = np.random.randn(100, 10) # simulates a global in a notebook + + class DataSimulator: + def __call__(self, theta): + import numpy as _np + idx = int(_np.random.randint(len(fixed_data))) + return fixed_data[idx] + theta.numpy() + + theta = torch.zeros(10) + result = ray.get(actor.run_callable.remote(_pack(DataSimulator), theta)) + assert result is not None + + +def test_large_global_closure(actor): + """Class closes over a large array; checks pickle size is reasonable.""" + large_array = np.random.randn(1000, 1000) # ~8 MB + + class LargeClosureSimulator: + def __call__(self, theta): + return large_array[0, :len(theta)] + theta.numpy() + + packed = _pack(LargeClosureSimulator) + size_mb = len(packed) / 1e6 + # Warn if pickle is large but don't fail — just report + print(f" (pickle size: {size_mb:.1f} MB)", end="") + + theta = torch.zeros(5) + result = ray.get(actor.run_callable.remote(packed, theta)) + assert result is not None + + +def test_class_redefinition(actor): + """Simulate re-running a notebook cell: new definition of the same name. + + The old class identity and the new one must be distinct, and the actor + always gets whatever was most recently packed. + """ + + class EvolvedSimulator: + version = 1 + def __call__(self, theta): + return theta * float(self.version) + + packed_v1 = _pack(EvolvedSimulator) + name_v1 = ray.get(actor.check_identity.remote(packed_v1)) + + # Simulate re-running the cell: redefine with a different version + class EvolvedSimulator: # noqa: F811 + version = 2 + def __call__(self, theta): + return theta * float(self.version) + + packed_v2 = _pack(EvolvedSimulator) + name_v2 = ray.get(actor.check_identity.remote(packed_v2)) + + # Both should be named EvolvedSimulator but they are distinct pickle blobs + assert name_v1 == "EvolvedSimulator" + assert name_v2 == "EvolvedSimulator" + + # Verify the actor actually executes the new version + theta = torch.tensor([1.0]) + result_v2 = ray.get(actor.run_callable.remote(packed_v2, theta)) + assert float(result_v2[0]) == 2.0, f"Expected 2.0, got {result_v2}" + + +def test_cuda_tensor_constructor_arg(actor): + """CUDA tensors stored as instance attributes FAIL across the boundary. + + Expected failure: cloudpickle serializes the CUDA storage, and the Ray + worker process cannot deserialize it without a matching GPU context. + Workaround: store plain numpy in __init__ and call .cuda() inside forward. + This test verifies the failure mode and that the workaround passes. + """ + if not torch.cuda.is_available(): + print(" (skipped — no CUDA)", end="") + return + + # Verify the failure mode + class SimBroken: + def __init__(self): + import torch as _torch + self.bias = _torch.tensor([1.0, 2.0]).cuda() + def __call__(self, theta): + return theta + self.bias.cpu() + + instance = SimBroken() + packed = _pack(instance) + theta = torch.zeros(2) + try: + ray.get(actor.run_callable.remote(packed, theta)) + raise AssertionError("Expected deserialization to fail — it did not") + except ray.exceptions.RayTaskError as e: + assert "CUDA" in str(e), f"Unexpected error: {e}" + print(" (CUDA storage fails as expected)", end="") + + # Verify the workaround pattern compiles correctly (CPU version of the pattern) + # The real workaround: store numpy in __init__, call .to(device) inside forward + class SimFixed: + def __init__(self): + import numpy as _np + self.bias_np = _np.array([1.0, 2.0]) + def __call__(self, theta): + import torch as _torch + bias = _torch.from_numpy(self.bias_np) # .cuda() only when device available + return theta + bias + + instance_fixed = SimFixed() + packed_fixed = _pack(instance_fixed) + result = ray.get(actor.run_callable.remote(packed_fixed, torch.zeros(2))) + assert result is not None + print(" (numpy workaround passes)", end="") + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + print("Cloudpickle / Ray serialization spike") + print("=" * 50) + + ray.init(ignore_reinit_error=True, logging_level="ERROR", namespace="falcon_spike") + + actor = WorkerActor.remote() + + tests = [ + ("basic callable (numpy return)", lambda: test_basic_callable_v2(actor)), + ("torch simulator (randn_like)", lambda: test_torch_simulator(actor)), + ("torch nn.Module (SmallMLP)", lambda: test_torch_nn_module(actor)), + ("transitive __main__ dep", lambda: test_transitive_dep(actor)), + ("closure over numpy array", lambda: test_closure_over_array(actor)), + ("closure over large array (~8 MB)", lambda: test_large_global_closure(actor)), + ("class redefinition (re-run cell)", lambda: test_class_redefinition(actor)), + ("CUDA tensor constructor arg", lambda: test_cuda_tensor_constructor_arg(actor)), + ] + + results = [] + for name, fn in tests: + ok = run_test(name, fn) + print() + results.append(ok) + + ray.shutdown() + + passed = sum(results) + total = len(results) + print("=" * 50) + print(f"Results: {passed}/{total} passed") + if passed == total: + print("Cloudpickle spike: PASSED — notebook classes survive to Ray actors.") + else: + print("Cloudpickle spike: PARTIAL — see failures above.") + return 0 if passed == total else 1 + + +if __name__ == "__main__": + sys.exit(main())